A primary-source reconstruction, 2015 to 2016

How the token standard was written

Between June 2015 and January 2016 the interface now carried by every Ethereum token was put together member by member, across a wiki page, a GitHub issue, a handful of gists, two sets of official documentation and one implementation repository. No single document contains its history. This page reconstructs it from the artifacts that survive.

The two members that define what ERC-20 is, allowance and Approval, arrived about thirty hours after the issue was filed and five months after the wiki page was started. The specification stopped moving on 6 January 2016. The first contract on mainnet carrying all six methods appeared four days later.

Carrying the six methods and behaving the way the standard requires are separate claims with separate dates. The first contract to deploy the interface appeared on 10 January 2016, the first to pair it with a real supply on 14 January, and the first to satisfy every requirement of EIP-20 as finalised on 20 March 2016. That last one was established by running the contracts, not by reading them.

Sources
GH Archive hourly event dumps, the ethereum/wiki git history, the GitHub gist history API, full clones of the relevant contract repositories, and a local export of mainnet create traces.
Recovered
19 body revisions of issue #20, eleven of them reconstructed here for the first time from events that embed the issue body.
Timestamps
All UTC. Author dates, not committer dates. Where a source records a local offset, the UTC conversion is shown.
Standard of proof
Every claim resolves to a commit SHA, a gist revision, a GitHub comment ID, a named GH Archive file, or a block number.

The interface

The final six, plus two events

function totalSupply() constant returns (uint256 supply)
function balanceOf(address _owner) constant returns (uint256 balance)
function transfer(address _to, uint256 _value) returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) returns (bool success)
function approve(address _spender, uint256 _value) returns (bool success)
function allowance(address _owner, address _spender) constant returns (uint256 remaining)

event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)

This is the interface as issue #20 finally stated it. Reading the trail requires holding two things apart: the name of a member and its signature. Several members reached their final name months before they reached their final parameter list, and one, transferFrom, was born as a typo and corrected twenty-four seconds later.

Three further members appear in almost every real token and in none of the required set: name, symbol and decimals. They were proposed as wallet rendering hints, marked optional at birth, and are still optional today.

Marks used in every table below

present
Present under its final name and with its final parameter signature.
~ partial
Name present, parameter signature differs from the final one.
absent
Not present.

What the reconstruction establishes

Findings

Each row is a question with a single dated answer and a single artifact. The dates are not approximate.

19recovered body revisions of issue #20
54revisions of the wiki page that preceded it
3,062contracts created on mainnet in the drafting window
0of them implement approve or allowance

Table scrolls sideways →

#QuestionAnswer (UTC)Artifact
1All six methods and both events first co-exist anywhere2015-11-20T15:53:42Zethereum/EIPs issue #20 body, revision 7. Prose spec, still carrying decimals and unapprove.
2First compilable Solidity containing all six and both events2015-11-30T21:06:17ZConsenSys/Tokens Token.sol at 4ba2396. Still declares unapprove.
3First file containing exactly the final interface, nothing extra2015-12-21T15:55:57ZConsenSys/Tokens Token.sol at c3a3426, “Derp. Approve is not a noun.”
4First time the specification itself states exactly the final interfacebetween 2016-01-06T10:12:13Z
and 2016-01-06T10:28:48Z
Issue #20 body revision 15. The bound is the gap between two comments.
5Wiki mirrors the final interface2016-01-06T10:48:26Zethereum/wiki 02c64c1 by caktux, twenty minutes after the issue.
6Contracts deployed 2015-11-03 to 2016-01-06 implementing all sixNone. The maximum reached is three of six.3,062 contracts, 2,941 with runtime bytecode.
7Deployed instances of approve(address,uint256) or allowance(address,address) in that windowZero, by two independent detectors.Opcode walk and substring match, in exact agreement.
8First contract on mainnet that deploys the ERC-20 interface, meaning all six selectors are present in its dispatcher2016-01-10T00:21:08Z
block 824,235
0x99146Bab2bB34D9Ca49EC4f0c82De3E5789ae22e, four days after the spec froze. It carries the interface and is not a token: supply reads zero at every block checked, and it has never emitted a Transfer. Selector presence is a bytecode fact and says nothing about behaviour.
9First contract with the interface and a real supply, that is, the first that is a token rather than a bare interface2016-01-14T15:22:33Z
block 847,527
0x55b9a11c2e8351b4Ffc7b11561148bfaC9977855, Digix Gold 1.0. Supply 1,400,331,016,000 at block 847,528, the block after deployment, and unchanged at every block sampled since. It is not fully compliant with EIP-20. Its Transfer event indexes _value, so it emits four topics and an empty data field where the standard requires three topics and the value in data, and transferFrom reports the spender as _from rather than the owner.
10First ERC-20 Transfer event on mainnet2016-01-27T16:54:44Z
block 913,198
0xa04bf47F0E9D1745D254b9B89f304c7d7ad121Aa, elcoin. Deployer to another account, 1,000,000 units. Not a mint from the zero address. The contract is not compliant. Its own transfer, approve and transferFrom return false, move nothing and emit nothing. Every transfer it recorded was driven through a controller, not through the ERC-20 entry points.
11First contract whose dispatcher is exactly the six and nothing else2016-01-28T14:01:48Z
block 917,622
0x37Dca38b1CBB2Cd043910eC46fe82Ddb9e38F00d, 754 bytes. Under execution it satisfies every requirement of EIP-20 except one: a guard of && _value > 0 makes a zero-value transfer return false and emit nothing. That requirement is in EIP-20 as finalised and is absent from the 2015 text, so by the standard as it stood when this was deployed, it passes.
12First contract fully compliant with EIP-20 as finalised, tested by execution against all ten requirements2016-03-20T11:09:16Z
block 1,184,107
0xacFD9D15fA769EaBb68410c4c675Ff2030f26416, 2,356 bytes. An ether wrapper: deposit() mints, withdraw(uint256) burns, totalSupply() returns the contract's own ether balance. It was never used, holding one transaction in its life, its creation. Its byte-identical twin 0xd654bDD32FC99471455e86C2E7f7D7b6437e9179, 81 blocks later, is the one that saw traffic.
13The source in the Foundation's 2015-12-03 tutorialByte-identical to MistCoin's sourceGist 21935dc… equals gist revision 7bcfaef3. Both compile to MistCoin's exact bytecode.
14What MistCoin implements2 of 6 required methods, 1 of 2 required events, 3 of 3 optionalDecoded from deployed runtime bytecode.
On 19 November 2015, the day issue #20 was filed and the date normally cited as the birth of ERC-20, the proposal contained four of the six methods, an approve that took no amount, no allowance, no Approval event, and five members that do not exist in the standard today: decimals, unapprove, isApprovedFor, approveOnce and isApprovedOnceFor.

Every dated event, all sources interleaved

The timeline

Fifty-eight entries, from the first official token tutorial to the standard's formal adoption. Open any entry for the underlying evidence: the source as it stood, the commit diff, the issue body, the decoded bytecode. Bold markers are the milestones from the findings above.

23 February 2015 – 3 November 2015

The wiki page and the guides

Before there was an issue there was a page. Standardized_Contract_APIs described currencies, exchanges and registries together, and its currency section is the direct ancestor of ERC-20. Over its first 35 revisions its vocabulary is replaced word by word, mostly by people renaming each other's functions. Running alongside it, and never quite agreeing with it, is the official documentation: the Frontier Guide for the first live network, and the ethereum.org token page. Both taught a vocabulary of their own.

  1. 2015-02-2312:59:40Z
    2015-02-23 · 12:59:40Z A coin contract with no standard API Gav Wood commits coin.sol to ethereum/dapp-bin. It is a currency contract written before anyone proposed that currency contracts should agree on anything. Gav Woodethereum/dapp-bin

    Included as the floor of the trail. Nothing in this file survives into the standard, and that is the point: the problem the wiki page was created to solve is visible here as an absence.

    Sources

  2. 2015-05-0422:09:49Z
    22:11:37Z
    2015-05-04 · 22:09:49Z and 22:11:37Z The Frontier Guide adds a token chapter Taylor Gerring creates contract_coin.md in the Ethereum Frontier Guide, then creates the page it transcludes. The contract uses sendToken and getBalance. Six weeks before Standardized_Contract_APIs exists. Taylor Gerringethereum/frontier-guidego-ethereum.wiki0 of 6

    The Frontier Guide is the official Ethereum user documentation for the first live network. It is a GitBook, and it holds almost no prose of its own: chapters are one-line transclusions of pages in the go-ethereum wiki. contract_coin.md in full, as created:

    # Coin contract

    One hundred and eighteen seconds later Gerring created the wiki page it would point at, and rewrote the chapter to pull it in:

    frontier-guide contract_coin.md at 8c91460, 2015-05-04T22:11:47Z
    {% include "git+https://github.com/ethereum/go-ethereum.wiki.git/Coin-Contract-Tutorial.md" %}

    That transclusion is why the guide's own repository contains no token vocabulary at any point in its history: 216 commits between 2015-04-30 and 2015-07-28, and a search of every revision for sendCoin, balanceOf, coinBalance or the word “token” returns nothing. The content lives in the wiki.

    The contract the official guide shipped

    go-ethereum.wiki Coin-Contract-Tutorial.md at cc23634, verbatim
    contract token { 
        mapping (address => uint) balances;
    
        // Initializes contract with 10 000 tokens to the creator of the contract
        function token() {
            balances[msg.sender] = 10000;
        }
        // Very simple trade function
        function sendToken(address receiver, uint amount) returns(bool sufficient) {
            if (balances[msg.sender] < amount) return false;
            balances[msg.sender] -= amount;
            balances[receiver] += amount;
            return true;
        }
    
        // Check balances of any account
        function getBalance(address account) returns(uint balance){
            return balances[account];
        }
    }

    This is a third vocabulary, and it belongs to neither of the others. Not the wiki's sendCoin and coinBalanceOf, which do not exist yet. Not the standard's transfer and balanceOf, which are five months away. The official guide for the first live Ethereum network taught sendToken and getBalance, and it did so before there was any standardization effort to disagree with.

    There is no event of any kind. Nothing a client could watch.

    The selectors, and where they can be seen

    sendToken(address,uint256)   0x412664ae
    getBalance(address)          0xf8b2cb4f

    Both appear in the compiled bytecode the tutorial pastes underneath the source, in its dispatcher: …90048063412664ae1461003a578063f8b2cb4f1461005257005b…. The published bytecode and the published source agree.

    Sources

  3. 2015-06-1716:06:56Z
    2015-06-17 · 16:06:56Z Vitalik Buterin creates Standardized_Contract_APIs The page that becomes ERC-20 begins as a three-part document covering currencies, exchanges and registries. Its currency API has balance, send, and an approve that takes a boolean. Zero of the final six. vbuterinethereum/wikirevision 1 of 54

    The framing sentence of the page states the whole design intent, and it is worth reading before anything else in this trail:

    Although Ethereum allows developers to create absolutely any kind of application without restriction to specific feature types, and prides itself on its "lack of features", there is nevertheless a need to standardize certain very common use cases in order to allow users and applications to more easily interact with each other.Standardized_Contract_APIs, revision 1, verbatim

    The currency API as created

    balance(address addr) returns (uint256 bal)
    send(address to, uint256 value) returns (bool success)
    send(address to, uint256 value, address from) returns (bool success)
    
    approve(address addr, bool status)
    approved(address addr) returns (bool status)
    approve_once(address addr, bool status, uint256 maxval)

    Two structures already present here survive all the way to the finished standard. The first is the pull model: a third-party spender is authorised in advance, then moves funds on the owner's behalf. The second is the paragraph justifying it, which travels almost unedited into the issue five months later:

    The third command is used for a "direct debit" workflow, allowing contracts to charge fees in sub-currencies; the second send command should fail unless the from account has deliberately authorized the sender of the message via some mechanism; we propose these standardized APIs for approvalStandardized_Contract_APIs, revision 1, verbatim

    What is not here: no events at all, no totalSupply, no read of another account's balance, and an approval that is a boolean switch rather than an amount.

    Sources

  4. 2015-06-1803:37:28Z
    2015-06-18 · 03:37:28Z Rewritten as the sendCoin API Eleven hours after creating it, Buterin replaces the whole currency section. balance becomes coinBalanceOf, send becomes sendCoin, and the approval members take the names they will keep for the next five months. vbuterinethereum/wiki

    This revision installs the vocabulary that the rest of 2015 runs on: sendCoin, coinBalanceOf, isApprovedFor, approveOnce, isApprovedOnceFor. Every one of these names is eventually replaced, and every replacement is dated in this timeline.

    Sources

  5. 2015-06-2307:46:09Z
    2015-06-23 · 07:46:09Z The first event enters the specification CoinSent(address,uint256,address). The ancestor of Transfer, with the value in the middle and the recipient last. vbuterinethereum/wiki

    Two renames separate this from the final event. It becomes CoinTransfer on 5 July 2015 and Transfer on 4 October 2015. The parameter order also changes: the deployed Transfer topic is (from, to, value), not (from, value, to).

    The CoinSent form was not merely a draft. It reached mainnet: two contracts deployed in the 2015-11-03 to 2016-01-06 window still carry its topic hash.

    Sources

  6. 2015-07-0221:51:06Z
    2015-07-02 · 21:51:06Z coinBalanceOf enters the Frontier Guide tutorial “Updated contracts with latest google docs.” The tutorial gains coinBalanceOf and one mention of sendCoin, while still carrying sendToken and getBalance elsewhere. The page is briefly bilingual. Alexandre Van de Sandego-ethereum.wiki

    This is the first appearance of the wiki standardization vocabulary inside the official user guide, and it is partial. Counting occurrences across the page at this revision: sendToken 12, coinBalanceOf 5, getBalance 2, sendCoin 1. The token contract itself has been rewritten around a public coinBalanceOf mapping but has not yet regained a transfer function or an event.

    go-ethereum.wiki Contract-Tutorial.md at 3efbb93, contract opening
    contract token { 
        mapping (address => uint) public coinBalanceOf;
    
        /* Initializes contract with 10 000 tokens to the creator of the contract */
        function token() {
            coinBalanceOf[msg.sender] = 10000;
        }

    Declaring the mapping public is what makes coinBalanceOf(address) callable: the getter is generated, not written. Its selector, 0xbbd39ac0, is the same one the wiki specification asks for. That coincidence is what makes the onchain census below readable at all, and also what makes it ambiguous.

    The commit message is the useful part. “latest google docs” places the drafting somewhere outside version control, which is consistent with the rest of this trail: almost nothing here was designed in the repository it ended up in.

    Sources

  7. 2015-07-1508:52:32Z
    10:23:14Z
    2015-07-15 · 08:52:32Z and 10:23:14Z ConsenSys/Tokens restarts, and implements the wiki Simon de la Rouviere restarts the repository with “Let there be tokens.”, then ninety minutes later commits the first Solidity implementation of the wiki API. This repository tracks the specification revision by revision for the next year. Simon de la RouviereConsenSys/Tokens

    ConsenSys/Tokens is the implementation lineage of ERC-20, and it starts four months before issue #20 exists. Its abstract Token.sol is the file that eventually holds the exact final interface, on 21 December 2015.

    Sources

  8. 2015-07-1515:38:24Z
    2015-07-15 · 15:38:24Z ethereum.org publishes a token page Van de Sande adds token.md to the ethereum.org source. It carries coinBalanceOf, sendCoin and event CoinTransfer, and links the wiki as the “Meta coin standard”. Nine days before the same contract reaches the go-ethereum wiki. Alexandre Van de Sandeethereum/ethereum-org0 of 6

    This is the page reached at ethereum.org/token, and it is the third piece of official documentation in this trail, alongside the Frontier Guide and the wallet's own ABI. It is the one Van de Sande points the issue thread to eight months later, when he writes that “the latest proposed standard is kept updated at ethereum.org/token”.

    ethereum-org views/content/token.md at 33b1217, contract as published
    contract token { 
        mapping (address => uint) public coinBalanceOf;
        event CoinTransfer(address sender, address receiver, uint amount);
    
        /* Initializes contract with initial supply tokens to the creator of the contract */
        function token(uint supply) {
            coinBalanceOf[msg.sender] = (supply || 10000);
        }
    
        /* Very simple trade function */
        function sendCoin(address receiver, uint amount) returns(bool sufficient) {
            if (coinBalanceOf[msg.sender] < amount) return false;
            coinBalanceOf[msg.sender] -= amount;
            coinBalanceOf[receiver] += amount;
            CoinTransfer(msg.sender, receiver, amount);
            return true;
        }
    }
    This is the contract the Frontier Guide would carry, and ethereum.org has it first. On this date the go-ethereum wiki tutorial that the guide transcludes still has coinBalanceOf with no event and no sendCoin in the contract; it does not reach this form until Van de Sande's rewrite on 2015-07-24, nine days later. The same author wrote both.

    What the page said the standard was

    [Meta coin standard](https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs) is a proposed standardization of function names for coin and token contracts, to allow them to be automatically added to other ethereum contract that utilizes trading, like exchanges or escrow.ethereum.org/token, Learn More section, verbatim including its markdown link syntax

    A pointer to the wiki, under the name “Meta coin standard”. It stays on the page for five months and is removed on 23 December 2015.

    Sources

  9. 2015-07-2414:51:33Z
    16:24:55Z
    2015-07-24 · 14:51:33Z and 16:24:55Z The guide's token contract reaches its final form Van de Sande's rewrite drops sendToken entirely and adds event CoinTransfer. Ninety-three minutes later zelig updates the guide's section anchors “for Alex tutorial updates”. Six days before Frontier launches. Alexandre Van de SandeViktor Tróngo-ethereum.wikiethereum/frontier-guide

    After this edit the page contains coinBalanceOf 35 times, sendCoin 14 times, and sendToken zero. The token contract is now this, and the source does not change again until a one-operator compile fix four weeks later:

    go-ethereum.wiki Contract-Tutorial.md, section “The Coin”, verbatim including its irregular indentation
    contract token { 
        mapping (address => uint) public coinBalanceOf;
        event CoinTransfer(address sender, address receiver, uint amount);
      
      /* Initializes contract with initial supply tokens to the creator of the contract */
      function token(uint supply) {
            coinBalanceOf[msg.sender] = (supply || 10000);
        }
      
      /* Very simple trade function */
        function sendCoin(address receiver, uint amount) returns(bool sufficient) {
            if (coinBalanceOf[msg.sender] < amount) return false;
            coinBalanceOf[msg.sender] -= amount;
            coinBalanceOf[receiver] += amount;
            CoinTransfer(msg.sender, receiver, amount);
            return true;
        }
    }
    The guide and the specification agree on two names and disagree on the signature of one of them. The wiki specifies sendCoin(uint _value, address _to). The guide writes sendCoin(address receiver, uint amount). Same name, reversed parameters, different ABI selector, no interoperability.
    guide  sendCoin(address,uint256)              0x90b98a11
    wiki   sendCoin(uint256,address)              0xc86a90fe
    
    both   coinBalanceOf(address)                 0xbbd39ac0
    guide  CoinTransfer(address,address,uint256)  0x16cdf170…6146

    The coinBalanceOf getter matches. The transfer function does not. A contract written from the guide and a contract written from the specification could read each other's balances and could not move each other's tokens.

    Note also what the event is not. The wiki had specified indexed parameters since June; this one indexes nothing, so a client cannot filter its logs by sender or recipient. And it is named CoinTransfer, which the wiki had already renamed from CoinSent on 5 July and would rename to Transfer on 4 October. The guide never follows.

    The guide follows, ninety-three minutes later

    frontier-guide contract_coin.md at 7366342, complete
    {% sections "the-coin", "" %}
    {% endsections %}
    
    {% include "git+https://github.com/ethereum/go-ethereum.wiki.git/Contract-Tutorial.md" %}

    The commit message is “fix section anchors for Alex tutorial updates”. The guide had been pulling the section anchored "coin"; the rewrite renamed the heading, so the anchor became "the-coin". That one-word edit is the entire mechanism by which the official Frontier documentation adopted this contract.

    Sources

  10. 2015-08-0720:42:23Z
    2015-08-07 · 20:42:23Z · block 49,853 The guide's contract on mainnet Eight days after Frontier launch. 264 of the 628 token-vocabulary contracts deployed on mainnet in 2015 carry coinBalanceOf together with the CoinTransfer topic, the guide's exact pairing. Seven carry the specification's sendCoin. mainnet264 contracts42% of 2015 token vocabulary

    Measured across every contract created on mainnet from Frontier launch on 2015-07-30 to 2015-12-31: 6,187 create traces, 5,724 with runtime bytecode, 628 carrying token vocabulary.

    In 2015Contracts
    coinBalanceOf(address)274
    CoinTransfer(address,address,uint256)287
    Both together, the guide's pairing264
    …of those, also carrying sendCoin(uint256,address)7

    The first six

    UTCBlockAddressRuntimeFamily
    2015-08-07T20:42:23Z49,8530x8374f5CC22eDA52e960D9558fb48DD4b7946609a49521c82a42
    2015-08-07T20:45:00Z49,8640x3B4446ACD9547D0183811F0E7c31b63706295f5249521c82a42
    2015-08-07T20:50:03Z49,8880xD958b51bC95338D152D55BEEd17a156e8aeC4c9f607b9184117
    2015-08-08T11:01:11Z53,0510x3c401B518252aBE3BBBf898A44939699E7dA163449521c82a42
    2015-08-08T11:01:38Z53,0540x33e98638ea7F2C2fd83731528fb53802Af395D1349521c82a42
    2015-08-08T17:41:09Z54,5370xE9712E9d4635f4c6937E9982A1596C64F0968A4c49521c82a42

    None of the 264 has name, symbol or decimals, and none has any of the final six. They cluster tightly by bytecode: 91 in one family, 54 in a second, 46 in a third, and runtime sizes of 495 bytes (99 contracts), 278 (66) and 238 (46). By month: 65 in August, 51 in September, 102 in October, 12 in November, 34 in December.

    This exposes a gap in the original scan. Its superseded-vocabulary list was built from the wiki, dapp-bin and the DAO, so it tested sendCoin(uint256,address) and never tested the guide's sendCoin(address,uint256). That is why the drafting-window figures elsewhere on this page report 56 contracts with coinBalanceOf and only 2 with sendCoin: 53 of those 56 pair coinBalanceOf with CoinTransfer, and none of the 53 carries the specification's sendCoin at all. Their transfer function is under a signature the scan did not look for.

    What is established is the co-occurrence and its size. The attribution to the guide is an inference, though a tight one: the pairing of a public coinBalanceOf mapping with an unindexed CoinTransfer event, no optional metadata, and no specification sendCoin, is exactly the shape of the contract printed in the official tutorial and of nothing else in the vocabulary. It has not been re-verified by re-scanning, because the archived corpus stores decoded member lists rather than bytecode. Re-running the walk with 0x90b98a11 added would settle it.

    Sources

    • Corpusraw/onchain/y2015-token-vocabulary-hits.json (628 records), window-token-vocabulary-hits.json (320 records)
    • Scan vocabularyraw/onchain/scan-selectors.py, LEGACY map
    • Missing selectorsendCoin(address,uint256) = 0x90b98a11
  11. 2015-08-2219:23:23Z
    2015-08-22 · 19:23:23Z A compile fix, and the last change to the contract Eerik Puska: “Solc gave error ‘Type error: Operator || not compatible with types uint256 and int_const’, fixed code to make it compile”. After this edit the guide's token contract is byte-identical through 2019. Eerik Puskago-ethereum.wikifinal form

    The fix is coinBalanceOf[msg.sender] = (supply || 10000); becoming coinBalanceOf[msg.sender] = supply;. The default-supply idea is dropped rather than rewritten. The reported error means the contract as published at Frontier launch did not compile under the solc of the day, and stood that way for twenty-three days after launch, and twenty-nine days after it was written.

    The token contract source across every revision of the page

    UTCCommitAuthorSource state
    2015-05-09T20:50:35Z49baa0aViktor TrónsendToken / getBalance, no event
    2015-07-02T21:51:06Z3efbb93Alexandre Van de Sandepublic coinBalanceOf mapping
    2015-07-24T14:51:33Z8eaddd0Alexandre Van de SandesendCoin(address,uint) + CoinTransfer
    2015-08-22T19:23:23Z4c31226Eerik Puskacompile fix. Unchanged from here
    2016-03-31T16:56:51Z8bab868Péter Szilágyia missing + in a console.log, outside the contract
    2016-10-31T18:29:02Zf49ff0eFelix Langeno change to the section at all
    2017-12-20T11:07:43Z620afaeFelix Lange“Delete legacy documentation”

    Between 2015-08-22 and 2016-10-31 the whole “The Coin” section changed by exactly one character, and the contract source not at all. Verified by extracting the section from each of the 29 revisions of the page and hashing it.

    The official Ethereum client documentation never adopted ERC-20. The wiki renamed coinBalanceOf to balanceOf and CoinTransfer to Transfer on 4 October 2015. The specification froze on 6 January 2016. The guide's tutorial went on printing coinBalanceOf, sendCoin and CoinTransfer unchanged for another twenty-two months, until the page was marked legacy in December 2017.

    Placed beside the onchain census, this is the plainest available explanation for a number that otherwise looks strange: 53 contracts emitting CoinTransfer during the drafting window, months after the standard had renamed it. They were following the documentation, and the documentation had not moved.

    Sources

    • Compile fixgo-ethereum wiki 4c31226
    • Legacy marker620afae, 2017-12-20T11:07:43Z, “Delete legacy documentation”
    • MethodSection extracted from all 29 revisions of Contract-Tutorial.md in a fresh clone and hashed; the contract body isolated by regex and hashed separately.
  12. 2015-08-2414:35:48Z
    2015-08-24 · 14:35:48Z The approval system reaches its largest form caktux adds disapprove, isApprovedOnce, isApprovedOnceFor, and the events AddressApproval and AddressApprovalOnce. The approval system now has six members. It ends with two. caktuxethereum/wiki

    This is the high-water mark of the approval design. Both events added here reached mainnet: one contract in the drafting window carries the AddressApproval topic and one carries AddressApprovalOnce.

    On 2 September 2015 Simon de la Rouviere renames disapprove to unapprove, the form that survives into issue #20 and is not removed until 6 January 2016.

    Sources

  13. 2015-09-0612:04:52Z
    2015-09-06 · 12:04:52Z The wiki API as runnable Solidity standardized_contract_apis/currency.sol lands in ethereum/dapp-bin. Zero of the final six, by signature. vbuterinethereum/dapp-bin

    The interface at that commit

    function currency()
    function sendCoin(uint _value, address _to)
    function sendCoinFrom(address _from, uint _value, address _to)
    function coinBalance()
    function coinBalanceOf(address _addr)
    function approve(address _addr)
    function isApproved(address _proxy)
    function approveOnce(address _addr, uint256 _maxValue)
    function isApprovedOnceFor(address _target, address _proxy)
    function disapprove(address _addr)
    event CoinSent(address indexed from, uint256 value, address indexed to)

    Not one member here carries a name that ERC-20 requires. This file is worth keeping in view when reading the onchain section: fifty-six contracts deployed during the drafting window implement coinBalanceOf, months after the wiki had renamed it away.

    Sources

  14. 2015-10-0120:38:26Z
    2015-10-01 · 20:38:26Z Alex Van de Sande proposes the optional three A new section, “Variables”, adds coinSymbol, coinName and coinBaseUnit to a page that until then described only methods and events. He marks them optional in the same sentence he introduces them. Alexandre Van de Sandeethereum/wikirevision 27

    These three members are invisible in the finished EIP-20 text precisely because they are the three the standard marks OPTIONAL. They are also, in practice, the three that most token contracts of the era actually implemented.

    The section as added, from the diff against revision 26

    ethereum/wiki 1f0f0a5, abridged where marked
    ### Variables
    
    These variables contain information about the coin. They are optional but adding
    them would increase the experience of the user that the GUI Client can use or not.
    
    #### coinSymbol (string)
    Contains a short sequence of letters that are used to represent the unit of the coin.
    [...] Examples: `USDX`, `BOB$`, `Ƀ`, `% of shares`.
    
    #### coinName (string)
    Contains a longer sequence of the coin name.
    [...] Examples: `e-Dollar`, `BobCoin`, `Bitcoin-Eth`.
    
    #### coinBaseUnit (integer)
    Although most coins are displayed to the final user as containing decimal points,
    coin values are unsigned integers, as the recommended method is to to calculations
    in the smallest possible unit. The client should always display the total units
    divided by coinBaseUnit. [...]
    
    Example: Bob has a balance of 100000 BobCoins, whose base unit is 100.
    His balance will be displayed on the client as **BOB$100.00**

    Three things in this text survive into the standard unchanged. The members themselves. Their optionality, written 49 days before issue #20 was filed. And their justification: these were never protocol features, they were wallet rendering hints.

    How the third member got its final name

    UTCSourceForm
    2015-10-01T20:38:26Zwiki 1f0f0a5, Van de SandecoinSymbol, coinName, coinBaseUnit
    2015-10-04T15:07:06Zwiki 607b6ac, Gav Woodname, symbol, baseUnit
    2015-10-30T12:51:31Zmeteor-dapp-wallet 860b85ftokenName, tokenSymbol, tokenDecimals
    2015-10-30T18:04:44Zmeteor-dapp-wallet ce24214name, symbol, decimals

    decimals never appears on the wiki page at all. The wiki carried baseUnit unchanged from revision 27 through revision 40. The word decimals enters the record from the wallet side and travels from there into issue #20.

    Sources

  15. 2015-10-0415:07:06Z
    2015-10-04 · 15:07:06Z Gav Wood renames coinBalanceOf to balanceOf, and CoinTransfer to Transfer Two of the eight final names arrive in a single wiki edit whose commit message is “Updated Standardized_Contract_APIs (markdown)”. Both keep their final signature from this moment onward. Gav Woodethereum/wikirevision 28

    This is the earliest public appearance of balanceOf and of the Transfer event anywhere in the sources examined. The same edit shortens Van de Sande's coinBaseUnit to baseUnit and drops the coin prefix from the other two optional members.

    The full interface after this edit

    sendCoin(uint _value, address _to)
    sendCoinFrom(address _from, uint _value, address _to)
    balanceOf(address _addr)
    approve(address _addr)
    unapprove(address _addr)
    isApprovedFor(address _target, address _proxy)
    approveOnce(address _addr, uint256 _maxValue)
    isApprovedOnceFor(address _target, address _proxy)
    
    event Transfer(address indexed from, address indexed to, uint256 value)
    event AddressApproval(address indexed address, address indexed proxy, bool result)
    event AddressApprovalOnce(address indexed address, address indexed proxy, uint256 value)

    Note what has and has not happened. balanceOf and Transfer are final. The two transfer methods are still sendCoin and sendCoinFrom, and still take value before recipient. There is no totalSupply, no allowance, and approve takes no amount.

    A second, cosmetic revision follows nineteen seconds later at 15:07:25Z.

    Sources

  16. 2015-10-0612:57:06Z
    12:57:30Z
    2015-10-06 · 12:57:06Z and 12:57:30Z Simon de la Rouviere adds transfer and transferFrom, twenty-four seconds apart The commit message is “Function names were still ‘coin’ related.” sendCoin becomes transfer. sendCoinFrom becomes trasnferFrom, misspelled. The typo is fixed in the next revision, 24 seconds later. Simon de la Rouviereethereum/wikirevisions 30 & 31

    The function is named in a wiki edit that also misspells its sibling. The correction commit message is “Typo fix”.

    After the typo fix, revision 31

    transfer(uint _value, address _to)
    transferFrom(address _from, uint _value, address _to)
    balanceOf(address _addr)
    approve(address _addr)
    unapprove(address _addr)
    isApprovedFor(address _target, address _proxy)
    approveOnce(address _address, uint256 _maxValue)
    isApprovedOnceFor(address _target, address _proxy)
    
    event Transfer(address indexed from, address indexed to, uint256 value)
    event AddressApproval(address indexed address, address indexed proxy, bool result)
    event AddressApprovalOnce(address indexed address, address indexed proxy, uint256 value)

    The names are now final but the signatures are not. transfer still takes value first and recipient second, which is the reverse of the standard. That is corrected on 28 October 2015 by Fabian Vogelsteller, and only then do these two members reach the form the ABI selectors are computed from.

    Sources

  17. 2015-10-0621:37:39Z
    2015-10-06 · 21:37:39Z The wallet's token interface Alex Van de Sande adds tokenABI.js to ethereum/meteor-dapp-wallet. It expects three members: sendCoin, coinBalanceOf, and the CoinTransfer event. Nine hours after the wiki renamed all three away. Alexandre Van de Sandemeteor-dapp-wallet

    This file is the interface the Ethereum Wallet, shipped inside Mist, actually required of a token. It is the operative definition of “token” for the entire period covered here, and it never required the six. Its full history:

    UTCCommitMembers in the ABI
    2015-10-06T21:37:39Z43a5115sendCoin, coinBalanceOf, CoinTransfer (event)
    2015-10-23T13:44:44Zcc1be08balanceOf, transfer, Transfer (event)
    2015-10-27T14:40:33Z518db2dbalances, balanceof, transfer, Transfer (event)
    2015-10-30T12:51:31Z860b85fbalanceOf, transfer, tokenDecimals, tokenName, tokenSymbol, Transfer (event)
    2015-10-30T18:04:44Zce24214balanceOf, transfer, decimals, name, symbol, Transfer (event)
    2015-11-03T12:06:23Z0022e37name, decimals, balanceOf, symbol, transfer, Transfer (event)

    518db2d contains the typo balanceof alongside balances, preserved here as found. After 2 December 2015 the file moved to tokenInterface.js with the same five members plus the event.

    The wallet wanted name, symbol and decimals, none of which ERC-20 requires. It did not want totalSupply, transferFrom, approve or allowance, four of the six that it does. The wallet's requirement and the specification's requirement were never the same document, and were never reconciled during this period.

    Sources

    • Raw artifactsraw/code-snapshots/mist-wallet-43a5115-tokenABI.js, mist-wallet-cc1be08-tokenABI.js, mist-wallet-0022e37-tokenABI.js
  18. 2015-10-2309:41:56Z
    2015-10-23 · 09:41:56Z · block 426,661 First contract on mainnet with balanceOf, transfer and Transfer together 0x3C655ccb35666579511489af88153517fc58b017, 508 bytes. The first of nine contracts from a two-address prototyping run over the following week. None of the nine has a name, a symbol or a decimal place. mainnetcreate trace

    Established against every contract created on mainnet from Frontier launch on 2015-07-30 to 2015-12-31: 6,187 create traces, 5,724 with runtime bytecode, 628 carrying token vocabulary.

    The prototyping run

    UTCBlockAddressRuntimeFamilyDeployer
    2015-10-23T09:41:56Z426,6610x3C655ccb35666579511489af88153517fc58b0175086ec841c70xB1a2B43A7433dd150BB82227eD519Cd6b142d382
    2015-10-23T12:18:34Z427,1980xe6512959d9cAA531c260E97C731BA0C821EC0C135086ec841c70xB1a2B43A7433dd150BB82227eD519Cd6b142d382
    2015-10-23T12:30:46Z427,2480xCAe62D22E8480b230d7aD93039167a0FfA7A2B8B6250316932e0xB1a2B43A7433dd150BB82227eD519Cd6b142d382
    2015-10-23T12:34:58Z427,2650x11485C5f164d6A67A72eEE9093b2581D1c3040946250316932e0xB1a2B43A7433dd150BB82227eD519Cd6b142d382
    2015-10-26T16:09:34Z443,4230xE274d18EF7b194A1EDEbB04cfE297CFe1489ef656250316932e0x9b22a80D5c7B3374a05b446081f97d0A34079e7F
    2015-10-26T16:11:46Z443,4290x00576287D3263ba831C8cf0886f06537e0515A2C6250316932e0xB1a2B43A7433dd150BB82227eD519Cd6b142d382
    2015-10-27T11:21:38Z447,4320x27Cb40ce7EB4d078196923d608Eb903A17E0C0ED625c56ecb930xB1a2B43A7433dd150BB82227eD519Cd6b142d382
    2015-10-27T17:47:26Z448,8480x22D9d0D4c30f7ad097E46669F3D624923179e949611ede402000xB1a2B43A7433dd150BB82227eD519Cd6b142d382
    2015-10-30T11:05:29Z462,6630xe6EE69495B571e1042f760d7f34009164AFF87a221115c531840xB1a2B43A7433dd150BB82227eD519Cd6b142d382

    The second of those deployers, 0x9b22a80D5c7B3374a05b446081f97d0A34079e7F, is MistCoin's own, eight days later.

    These are not nine independent tokens. Eight come from one address, which deployed twelve token-vocabulary contracts between 23 October and 20 November 2015 across six distinct bytecode families: one developer iterating. The ninth comes from MistCoin's own deployer and is byte-identical to a contract 0xB1a2B43A7433dd150BB82227eD519Cd6b142d382 deployed two minutes and twelve seconds later from the same family. Two addresses put the same build on mainnet two minutes apart.

    The wallet's tokenABI.js at cc1be08, committed on the same day, opens with a commented-out address: //"0x11485C5f164d6A67A72eEE9093b2581D1c304094". That is row four of the table above. The client's token interface was being written against these prototypes as they were deployed.

    Sources

    • CorpusBigQuery crypto_ethereum.traces export, cross-checked against a local index of 12,023,046 contracts
    • Raw artifactsraw/onchain/y2015-token-vocabulary-hits.json, raw/code-snapshots/mist-wallet-cc1be08-tokenABI.js
  19. 2015-10-2813:44:47Z
    2015-10-28 · 13:44:47Z Fabian Vogelsteller fixes the parameter order “changed order to parameters in transfer and transferFrom”. transfer(address _to, uint256 _value) and transferFrom(_from, _to, _value) reach their final signatures, three weeks after reaching their final names. Fabian Vogelstellerethereum/wikirevision 32

    This edit is what fixes the two ABI selectors that matter most: 0xa9059cbb for transfer and 0x23b872dd for transferFrom. Before it, the same names hash to different selectors and would not interoperate with anything deployed after.

    transfer(address _to, uint256 _value)
    transferFrom(address _from, address _to, uint256 _value)
    balanceOf(address _address)
    approve(address _address)
    unapprove(address _address)
    isApprovedFor(address _target, address _proxy)
    approveOnce(address _address, uint256 _maxValue)
    isApprovedOnceFor(address _target, address _proxy)
    
    event Transfer(address indexed from, address indexed to, uint256 value)
    event AddressApproval(address indexed address, address indexed proxy, bool result)
    event AddressApprovalOnce(address indexed address, address indexed proxy, uint256 value)

    Four of the six now stand in final form: balanceOf, transfer, transferFrom, and the Transfer event. Missing: totalSupply, allowance, an approve that takes an amount, and the Approval event.

    Sources

  20. 2015-10-3018:01:51Z
    2015-10-30 · 18:01:51Z An anonymous gist, and a rename in the wallet Gist 909d02feff3a2e59f714, “myToken”, one revision. Three minutes later the wallet ABI renames tokenName, tokenSymbol and tokenDecimals to name, symbol and decimals. gist 909d02…meteor-dapp-wallet

    This gist is the one the Ethereum Wallet 0.3.5 release notes link as their example token, four days later. It declares contract myToken, its transfer carries returns(bool success), and it has no overflow guard.

    Both differences matter, because the contract deployed as MistCoin on 3 November has the opposite of each: no return value, and an overflow check. The wallet release shipped pointing at a source that is not the one its own author deployed.

    Sources

  21. 2015-11-0311:26:01Z
    → 13:43:51Z
    2015-11-03 · 11:26:01Z to 13:43:51Z MistCoin is deployed Fabian Vogelsteller creates the MyToken gist at 11:26:01Z. Ethereum Wallet 0.3.5 is drafted twelve minutes later. MistCoin is deployed at 12:03:29Z. The gist revision it was compiled from is saved seventeen seconds after the block that contains the deployment. The wallet ships at 13:43:51Z. frozemangist 20c8b56…mainnetethereum/mist

    Sixteen days before issue #20 was filed, and two months before the specification stopped moving, the token that is most often called the first ERC-20 was put on mainnet. This is the day, minute by minute. Gist times come from the GitHub gist history API, release times from the GitHub releases API, and the deployment time from the block timestamp of its create trace.

    UTCEvent
    11:26:01Zfrozeman creates gist 20c8b5658349b003b08d, “MyToken solidity contract”. Revision bce55cca.
    11:28:43ZRevision 251c7324.
    11:28:56ZRevision 9f826f1a.
    11:37:56ZEthereum Wallet release 0.3.5 (Beta 3) created as a draft by frozeman. Eleven minutes and fifty-five seconds after the gist.
    11:59:12ZRevision 759cddeb removes returns (bool success) from transfer. Constructor is still (_supply, _name, _decimals, _symbol).
    12:03:29ZMistCoin deployed. Block 483,325, tx 0x74349ce6…54a7, deployer 0x9b22a80D5c7B3374a05b446081f97d0A34079e7F.
    12:03:46ZRevision 7bcfaef3 swaps the constructor to (_supply, _name, _symbol, _decimals). Final revision; the gist has not changed since.
    12:06:23ZWallet ABI adds name, decimals, symbol (0022e37).
    13:43:51ZEthereum Wallet 0.3.5 (Beta 3) published.
    18:27:06ZSecond contract with the full wallet-renderable token shape, 0x853737186cb24D4152f979B9152F652b67F7e9b7. Whitcoin is third at 18:46:18Z.
    MistCoin was deployed seventeen seconds before the gist revision whose source it was compiled from, and one hour forty minutes before the wallet release that made the feature public.

    The seventeen-second ordering is not a paradox. Recompilation shows the deployed bytecode carries revision 5's constructor argument order, not revision 4's: the source was edited locally, compiled, deployed, and saved back to the gist afterwards.

    The source, gist revision 7bcfaef3, contract body

    License header omitted. Verbatim otherwise, including the two typographic errors in the comments.
    contract MyToken {
        /* Public variables of the token */
        string public name;
        string public symbol;
        uint8 public decimals;
    
        /* This creates an array with all balances */
        mapping (address => uint256) public balanceOf;
    
        /* This generates a public event on the blockchain that will notify clients */
        event Transfer(address indexed from, address indexed to, uint256 value);
    
        /* Initializes contract with initial supply tokens to the creator of the contract */
        function MyToken(uint256 _supply, string _name, string _symbol, uint8 _decimals) {
            /* if supply not given then generate 1 million of the smallest unit of the token */
            if (_supply == 0) _supply = 1000000;
    
            /* Unless you add other functions these variables will never change */
            balanceOf[msg.sender] = _supply;
            name = _name;
            symbol = _symbol;
    
            /* If you want a divisible token then add the amount of decimals the base unit has  */
            decimals = _decimals;
        }
    
        /* Send coins */
        function transfer(address _to, uint256 _value) {
            /* if the sender doenst have enough balance then stop */
            if (balanceOf[msg.sender] < _value) throw;
            if (balanceOf[_to] + _value < balanceOf[_to]) throw;
    
            /* Add and subtract new balances */
            balanceOf[msg.sender] -= _value;
            balanceOf[_to] += _value;
    
            /* Notifiy anyone listening that this transfer took place */
            Transfer(msg.sender, _to, _value);
        }
    }

    That is the entire contract. One method, one event, three public variables. The transfer here declares no return value at all, which is why the deployed function returns nothing where EIP-20 specifies returns (bool success).

    Issue #20 did not exist on this day, and would not for another sixteen. See the MistCoin section for what the deployed bytecode contains and where the contract sits in the record.

    What the release notes said

    This release fixes a lot of bugs and adds a new custom Token system, as well as a simple way to deploy contracts right from the wallet!Ethereum Wallet 0.3.5 release notes, verbatim

    The notes never use the words “standard”, “ERC” or “EIP”. They describe tokens purely as a wallet feature, and they are explicit that names and symbols are not trustworthy identifiers:

    Tokens can have any names or symbols (including "US dollar" or "BTC" and other token names) but each one will have a single unique icon, this is your guarantee of sending and receiving the token you want.Ethereum Wallet 0.3.5 release notes, verbatim

    The release links its example token to the anonymous gist of 30 October, not to frozeman's own gist of that morning.

    Sources

17 November 2015 – 30 November 2015

The proposal

Two weeks in which the standard acquires its number, loses five members, and gains the two that define it. Everything here happens on a gist, an issue thread, a poll page and one repository, and most of it happens within seventy-two hours.

  1. 2015-11-1710:24:27Z
    2015-11-17 · 10:24:27Z The draft that becomes issue #20 Fabian Vogelsteller creates gist 090ae32041bcfe120824, “Token proposal”. Eight methods and three events, five of the eight methods being approval machinery. Two days later this text is posted as an EIP. frozemangist 090ae32…revision 1 of 6

    The gist is written in the same shape the issue will use: a method list with a prose paragraph for each. It is the document ethers cites two days later when updating the wiki, and it is the direct source of the issue body.

    Revision 1, complete method list

    transfer(address _to, uint256 _value) returns (bool success)
    transferFrom(address _from, address _to, uint256 _value) returns (bool success)
    balanceOf(address _address) constant returns (uint256 balance)
    
    approve(address _address) returns (bool _success)
    unapprove(address _address) returns (bool _success)
    isApprovedFor(address _target, address _proxy) constant returns (bool _r)
    approveOnce(address _address, uint256 _maxValue) returns (bool _success)
    isApprovedOnceFor(address _target, address _proxy) returns (uint256 _maxValue)
    
    Transfer(address indexed from, address indexed to, uint256 value)
    AddressApproval(address indexed address, address indexed proxy, bool result)
    AddressApprovalOnce(address indexed address, address indexed proxy, uint256 value)

    Five of the eight methods are the approval system: a full-custody approve, its unapprove, a query, a one-time variant and that variant's query. Of the five, exactly one survives, and only after being redefined. There is no totalSupply and no allowance.

    The paragraph introducing the approval members is the one Buterin wrote on the wiki seventeen months earlier, carried across almost unedited:

    The transferFrom method is used for a "direct debit" workflow, allowing contracts to send coins on your behalf, for example to "deposit" to a contract address and/or to charge fees in sub-currencies; the command should fail unless the _from account has deliberately authorized the sender of the message via some mechanism; we propose these standardized APIs for approval:Token proposal gist, revision 1, verbatim

    All six revisions

    RevUTCBytestotalSupplybalanceOftransfertransferFromapproveallowanceTransferApproval
    v12015-11-17T10:24:27Z2604~
    v22015-11-17T15:25:23Z2619~
    v32015-11-17T18:48:06Z2709~
    v42015-11-18T08:59:46Z2817~
    v52016-12-13T09:30:08Z2874~
    v62016-12-13T09:30:35Z2904~

    Revisions 5 and 6, thirteen months later, add only a banner: “This is outdated: The ERC-20 is here”. The gist never gains allowance or Approval. Those two members are born on the issue, not here.

    Sources

  2. 2015-11-1808:59:46Z
    2015-11-18 · 08:59:46Z totalSupply appears for the first time anywhere Gist revision 4. Unlike every other member of ERC-20, totalSupply is not a rename of something older. It is newly invented, 16 hours 29 minutes before the wiki has it and 24 hours 53 minutes before issue #20 is opened. frozemangist 090ae32…revision 4

    Every other member of the final eight can be traced to an ancestor with a different name. balanceOf was coinBalanceOf, transfer was sendCoin, allowance was isApprovedFor, Transfer was CoinTransfer and before that CoinSent. totalSupply has no ancestor.

    It reaches implementation code the same day, 4 hours 7 minutes later, in ConsenSys/Tokens.

    Sources

  3. 2015-11-1901:29:04Z
    01:31:08Z
    2015-11-19 · 01:29:04Z and 01:31:08Z The wiki is updated from the gist, and issue #19 is opened ethers copies the gist into the wiki with the message “updating from https://gist.github.com/frozeman/090ae32041bcfe120824”, then two minutes later opens issue #19, “APIs for Transferable Fungibles”. It is closed in favour of #20 nine hours later. ethersethereum/wikiethereum/EIPs

    For about eight hours there are two competing token EIPs. Issue #19 is opened at 01:31:08Z, issue #20 at 09:52:56Z, and #19 is closed at 10:36:26Z, less than an hour after #20 appears. The number that the standard carries for the rest of its life is decided in that window.

    Sources

  4. 2015-11-1909:52:56Z
    2015-11-19 · 09:52:56Z Issue #20 is opened: “ERC: Token standard” Four of the six methods, in final form. An approve that takes no amount. No allowance, no Approval event, and five members that are not in the standard today. This is the body exactly as posted, recovered from the IssuesEvent payload. frozemanethereum/EIPs #20revision 1 of 193,334 bytes
    GitHub did not record issue-body edit history until late 2016. The API returns diff: null for this issue and serves only the current text. The document that defines ERC-20 has no visible history at the place it lives. Everything below is recovered from GH Archive event payloads, which embed the entire parent issue object including its body as it stood at that instant. See Method.

    Members as posted

    function decimals()
    function totalSupply()
    function balanceOf(address _address)
    function transfer(address _to, uint256 _value)
    function transferFrom(address _from, address _to, uint256 _value)
    function approve(address _address)
    function unapprove(address _address)
    function isApprovedFor(address _target, address _proxy)
    function approveOnce(address _address, uint256 _maxValue)
    function isApprovedOnceFor(address _target, address _proxy)
    
    event Transfer(address indexed _from, address indexed _to, uint256 _value)
    event AddressApproval(address indexed _address, address indexed _proxy, bool _result)
    event AddressApprovalOnce(address indexed _address, address indexed _proxy, uint256 _value)

    Ten methods, of which four match the final standard by name and signature. Three events, of which one survives. Five members here do not exist in ERC-20 today: decimals, unapprove, isApprovedFor, approveOnce, isApprovedOnceFor.

    The body as posted, verbatim

    3,334 bytes. Recovered from the IssuesEvent/opened payload in data.gharchive.org/2015-11-19-9.json.gz. Reproduced complete, including the unclosed code fence in the original.
    The following describes standard functions a token contract can implement. Those will allow dapps and wallets to handle tokens across multiple interfaces/dapps.
    
    The most important here are, `transfer`, `balanceOf`, `decimals` and the `Transfer` event.
    
    ```js
    ## Token
    
    ### Methods
    
    #### decimals
    
    ```js
    function decimals() constant returns (uint256 decimals)
    ```
    Returns the number of decimal points this token requires, e.g. `2`
    
    
    #### totalSupply
    
    ```js
    function totalSupply() constant returns (uint256 supply)
    ```
    Get the total coin supply
    
    #### balanceOf
    
    ```js
    function balanceOf(address _address) constant returns (uint256 balance)
    ```
    Get the account balance of another account with address `_address`
    
    #### transfer
    
    ```js
    function transfer(address _to, uint256 _value) returns (bool _success)
    ```
    Send `_value` amount of coins to address `_to`
    
    #### transferFrom
    
    ```js
    function transferFrom(address _from, address _to, uint256 _value) returns (bool success)
    ```
    Send `_value` amount of coins from address `_from` to address `_to`
    
    The `transferFrom` method is used for a "direct debit" workflow, allowing contracts to send coins on your behalf, for example to "deposit" to a contract address and/or to charge fees in sub-currencies; the command should fail unless the `_from` account has deliberately authorized the sender of the message via some mechanism; we propose these standardized APIs for approval:
    
    #### approve
    
    ```js
    function approve(address _address) returns (bool success)
    ```
    Allow `_address ` to direct debit from your account with full custody. Only implement if absolutely required and use carefully. See `approveOnce` below for a more limited method.
    
    #### unapprove
    
    ```js
    function unapprove(address _address) returns (bool success)
    ```
    Unapprove address `_address ` to direct debit from your account if it was previously approved. Must reset both one-time and full custody approvals.
    
    #### isApprovedFor
    
    ```js
    function isApprovedFor(address _target, address _proxy) constant returns (bool success)
    ```
    Returns 1 if `_proxy` is allowed to direct debit from `_target`
    
    #### approveOnce
    
    ```js
    function approveOnce(address _address, uint256 _maxValue) returns (bool success)
    ```
    Makes a one-time approval for `_address ` to send a maximum amount of currency equal to `_maxValue`
    
    #### isApprovedOnceFor
    
    ```js
    function isApprovedOnceFor(address _target, address _proxy) returns (uint256 maxValue)
    ```
    Returns `_maxValue` if `_proxy` is allowed to direct debit the returned `maxValue` from address `_target` only once. The approval must be reset on any transfer by `_proxy` of `_maxValue` or less.
    
    ### Events
    #### Transfer
    
    ```js
    event Transfer(address indexed _from, address indexed _to, uint256 _value)
    ```
    Triggered when tokens are transferred.
    
    #### AddressApproval
    
    ```js
    event AddressApproval(address indexed _address, address indexed _proxy, bool _result)
    ```
    Triggered when an `_address` approves `_proxy` to direct debit from their account.
    
    #### AddressApprovalOnce
    
    ```js
    event AddressApprovalOnce(address indexed _address, address indexed _proxy, uint256 _value)
    ```
    Triggered when an `_address` approves `_proxy` to direct debit from their account only once for a maximum of `_value`
    
    ```

    Two sentences in that text are worth marking. The first line of the Motivation is the standard's actual purpose, and it does not mention exchanges, allowances or contract-to-contract accounting at all. The second is the ranking: “The most important here are, transfer, balanceOf, decimals and the Transfer event.” That sentence names decimals, which is removed from the standard seven days later, and it survives into the final text unedited, still naming decimals, long after the member itself is gone.

    Sources

  5. 2015-11-1915:33:05Z
    2015-11-19 · 15:33:05Z The issue gets a number Revision 4 adds an EIP header block to the top of the body. It is the first time the document identifies itself as ERC 20. The interface below it is unchanged: still four of six. frozemanethereum/EIPs #20revision 43,553 bytes

    The header, as it still reads in the final text

    ERC: 20
    Title: Token standard
    Status: Draft
    Type: Informational
    Created: 19-11.2015
    Resolution: https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs

    The malformed date, 19-11.2015, is in the original and is never corrected. The Resolution line points back at the wiki page, which at this moment does not carry the same interface as the issue and will not for another seven weeks.

    Sources

    • Snapshot carried bycomment 158091127, niran
    • GH Archive filedata.gharchive.org/2015-11-19-15.json.gz
    • Edit windowafter 2015-11-19T12:24:52Z, at or before 15:33:05Z
    • Raw artifactraw/issue20-bodies/r04-20151119T153305Z.md
  6. 2015-11-1916:31:57Z
    2015-11-19 · 16:31:57Z “Pave the cowpaths” Six hours and thirty-nine minutes after the issue is filed, Alex Van de Sande posts the design doctrine that keeps the standard small: implement what everyone already agrees on, and let real use decide the rest. alexvandesandeethereum/EIPs #20comment 158110210
    @ethers decimals, name and symbol are important for displaying to the end user. […] Regarding the approve/cheque discussion, I feel that we should always use focus on paving cow paths: implement what everyone is on absolute consensus as the basic "standard" and then allow real world usage dictate how to better define more advanced use cases.Verbatim, with the source's own ellipsis. Link text inlined.

    This is the argument that prevailed. The approval system was cut from five members to two, and the three optional members were kept. It is also the argument that produces the gap this whole reconstruction measures: the standard describes an allowance model that almost nobody implements, while the wallet ships a definition of “token” that almost everybody implements.

    Sources

  7. 2015-11-1919:07:35Z
    2015-11-19 · 19:07:35Z approve gains an amount Revision 5. approve(address _for, uint256 _value). approveOnce and isApprovedOnceFor are deleted. Five of the six methods now stand in final form, on the day the issue opened. frozemanethereum/EIPs #20revision 52,988 bytes

    The one-time approval is gone and the full-custody approval has been replaced by a capped one. The two ideas merged: instead of “approve with no limit” plus “approve once up to a maximum”, there is one approve that takes an amount. This is the single most consequential edit in the standard's history, and it happens nine hours and fifteen minutes after the issue was posted.

    function decimals()
    function totalSupply()
    function balanceOf(address _address)
    function transfer(address _to, uint256 _value)
    function transferFrom(address _from, address _to, uint256 _value)
    function approve(address _for, uint256 _value)
    function unapprove(address _address)
    function isApprovedFor(address _allowed, address _for)
    
    event Transfer(address indexed _from, address indexed _to, uint256 _value)
    event AddressApproval(address indexed _address, address indexed _proxy, bool _result)
    event AddressApprovalOnce(address indexed _address, address indexed _proxy, uint256 _value)

    The parameter names are not yet _spender. The query is still isApprovedFor. The two events are still the superseded ones. All of that changes the next afternoon.

    Sources

    • GH Archive filedata.gharchive.org/2015-11-19-19.json.gz
    • Edit windowafter 2015-11-19T19:00:41Z, at or before 19:07:35Z
    • Raw artifactraw/issue20-bodies/r05-20151119T190735Z.md
  8. 2015-11-2015:53:42Z
    2015-11-20 · 15:53:42Z allowance and Approval appear, and all eight members co-exist Revision 7. isApprovedFor becomes allowance. AddressApproval becomes Approval. Thirty hours after the issue was filed, the six methods and both events exist together in one document for the first time anywhere. frozemanethereum/EIPs #20revision 72,750 bytes

    The two members that make ERC-20 what it is, and that exchanges and later DeFi protocols are built on, are the last two to arrive. Both arrive as renames, in an edit between two comments three minutes and forty-four seconds apart.

    function decimals()
    function totalSupply()
    function balanceOf(address _address)
    function transfer(address _to, uint256 _value)
    function transferFrom(address _from, address _to, uint256 _value)
    function approve(address _spender, uint256 _value)
    function unapprove(address _spender)
    function allowance(address _address, address _spender)
    
    event Transfer(address indexed _from, address indexed _to, uint256 _value)
    event Approval(address indexed _address, address indexed _spender, uin256 _value)

    The uin256 in the Approval declaration is a typo in the original. It persists through revisions 7 to 13 and is not corrected until revision 14 on 2 December 2015.

    Two members that are not in the final standard are still here: decimals and unapprove. The parameter names are still _address rather than _owner. Revision 8, two minutes and twenty-four seconds later, is a thirteen-byte change.

    This is finding 1. Every earlier document is missing at least one member. Every later one is a refinement of this shape. If a single date is wanted for the interface, this one has a better claim than the day the issue was opened.

    Sources

    • Snapshot carried bycomment 158439737, frozeman
    • GH Archive filedata.gharchive.org/2015-11-20-15.json.gz
    • Edit windowafter 2015-11-20T15:49:58Z, at or before 15:53:42Z
    • Raw artifactraw/issue20-bodies/r07-20151120T155342Z.md
  9. 2015-11-2113:31:28Z
    2015-11-21 · 13:31:28Z The poll on which members to keep ethers creates a wiki page where participants list which members they want IN and which OUT. Twelve people vote over five days. decimals is the member most often listed OUT, and it is removed from the issue body five days later. ethersethereum/wiki9 revisions

    The page, verbatim

    ethereum/wiki, Poll for token proposal EIP 20, final state
    Poll for https://github.com/ethereum/EIPs/issues/20
    
    github username | IN       | OUT      | NextEIP*? | Comments
    ----------------|----------------|---------------|-----------------------------------|---------
    example           |  Set1, identifier | decimals, approve, unapprove, allowance | YES |
    christianlundkvist|  Set1             | decimals                                | YES |
    nmushegian        |  Set1             | decimals                                | YES |
    joeykrug          |  Set1, identifier | ?                                       | ?   |
    koeppelmann       |  Set1, identifier | ?                                       | ?   |
    Georgi87          |  Set1, identifier | decimals                                | Yes |
    niran             |  Set1             | decimals                                | YES?|
    ethers            |  Set1, identifier | decimals                                | YES |
    simondlr          |  Set1             | decimals                                | YES |
    frozeman          |  Set1, decimals   |                                         | NO  |
    alexvandesande    |  Set1, decimals   |                                         |     |
    caktux            |  Set1, decimals   |                                         |     |
    firecar96         |  Set1             | decimals, identifier                    | YES |
    
    
    * Set1 = balanceOf, transfer, transferFrom, totalSupply, approve, unapprove, allowance
    * "identifier" = https://github.com/ethereum/EIPs/issues/20#issuecomment-158436720 and the discussion
    * NextEIP means should the OUT items be in separate EIP/s or be in EIP20 itself but marked Optional.  YES means separate EIP/s, NO means keep in EIP20 and mark it Optional.

    Set1 is the final six plus unapprove. Nobody voted against unapprove except the example row, and it survived this poll. It was not dropped until approve was redefined as absolute, six weeks later.

    The three people who voted to keep decimals are frozeman, alexvandesande and caktux: the author of the proposal, the author of the optional three, and the person who maintained the wiki copy. They lost.

    Page revisions

    UTCAuthorMessageSHA
    2015-11-21T13:31:28ZethersCreated Poll for token proposal EIP 20 (markdown)0604d8f1
    2015-11-25T19:15:21ZethersUpdated Poll for token proposal EIP 20 (markdown)cad8db58
    2015-11-25T19:26:05ZethersUpdated Poll for token proposal EIP 20 (markdown)574050aa
    2015-11-25T19:29:14Zethersadd alexvandesande3af24c1d
    2015-11-25T19:31:25ZethersUpdated Poll for token proposal EIP 20 (markdown)f3713a7f
    2015-11-25T19:37:36Zethersbetter format with identifier61466494
    2015-11-25T19:38:33ZNiran BabalolaUpdated Poll for token proposal EIP 20 (markdown)41d4f528
    2015-11-25T22:45:22Zcaktuxdecimals5733dc2d
    2015-11-26T09:58:56ZFabian VogelstellerUpdated Poll for token proposal EIP 20 (markdown)e55064c5
    2015-11-26T09:59:13ZFabian VogelstellerUpdated Poll for token proposal EIP 20 (markdown)7074a665

    Sources

  10. 2015-11-2610:34:22Z
    2015-11-26 · 10:34:22Z decimals is removed from the specification Revision 11, 156 bytes smaller than revision 10. The member the wallet actually used, and the one the opening sentence names as most important, is voted out of the standard seven days after it was proposed. ethereum/EIPs #20revision 112,631 bytes

    Revision 10, sixty-nine minutes earlier, had already done the other cleanup: _address becomes _owner throughout, giving balanceOf(address _owner) and allowance(address _owner, address _spender) their final parameter names.

    Revision 11, members. Only unapprove is now non-standard.
    function totalSupply()
    function balanceOf(address _owner)
    function transfer(address _to, uint256 _value)
    function transferFrom(address _from, address _to, uint256 _value)
    function approve(address _spender, uint256 _value)
    function unapprove(address _spender)
    function allowance(address _owner, address _spender)
    
    event Transfer(address indexed _from, address indexed _to, uint256 _value)
    event Approval(address indexed _owner, address indexed _spender, uin256 _value)

    One member stands between this and the finished standard: unapprove. It takes another six weeks to remove.

    decimals is deleted from the specification but not from the sentence recommending it. “The most important here are, transfer, balanceOf, decimals and the Transfer event” is still in the body at revision 15, the first exact statement of the final interface, six weeks after the member itself was removed. The standard recommends a member it does not define.

    Sources

    • Snapshot carried bycomment 159872082, simondlr
    • GH Archive filedata.gharchive.org/2015-11-26-10.json.gz
    • Edit windowafter 2015-11-26T09:25:09Z, at or before 10:34:22Z
    • Raw artifactraw/issue20-bodies/r11-20151126T103422Z.md
  11. 2015-11-3021:06:17Z
    2015-11-30 · 21:06:17Z First compilable Solidity with all six and both events ConsenSys/Tokens Token.sol at 4ba2396, “Refactored to current standards.” Ten days after the interface first existed as prose, it exists as code. unapprove is still declared. Simon de la RouviereConsenSys/Tokens
    function totalSupply()
    function balanceOf(address _owner)
    function transfer(address _to, uint256 _value)
    function transferFrom(address _from, address _to, uint256 _value)
    function approve(address _spender, uint256 _value)
    function unapprove(address _spender)
    function allowance(address _owner, address _spender)
    
    event Transfer(address indexed _from, address indexed _to, uint256 _value)
    event Approval(address indexed _owner, address indexed _spender, uint256 _value)

    This is finding 2. Note that the typo in the specification, uin256, does not appear here: the implementation writes uint256, because it has to compile. The specification is corrected two days later.

    Sources

2 December 2015 – 28 January 2016

Settling the interface

The specification goes backwards before it goes forwards. Implementation reaches the final shape three weeks before the specification does, and mainnet reaches it four days after. This is also the period in which the Ethereum Foundation publishes a token tutorial that neither names the standard nor implements it.

  1. 2015-12-0209:28:55Z
    2015-12-02 · 09:28:55Z Regression: Approval is renamed Approved Revision 13 moves away from the final form. Approval becomes Approved, and a second event Unapproved is added. The standard is further from its final state on 2 December than it was on 20 November. frozemanethereum/EIPs #20revision 132,754 bytes

    The rename is consistent with the design at that moment: if unapprove is a real method, it needs its own event, and a pair called Approved and Unapproved is more symmetrical than Approval and Unapproved. The whole branch is abandoned five weeks later when approve is redefined as absolute and unapprove stops being needed.

    function totalSupply()
    function balanceOf(address _owner)
    function transfer(address _to, uint256 _value)
    function transferFrom(address _from, address _to, uint256 _value)
    function approve(address _spender, uint256 _value)
    function unapprove(address _spender)
    function allowance(address _owner, address _spender)
    
    event Transfer(address indexed _from, address indexed _to, uint256 _value)
    event Approved(address indexed _owner, address indexed _spender, uin256 _value)
    event Unapproved(address indexed _owner, address indexed _spender)

    Revision 14, fifty-three minutes later, is a one-byte change: the uin256 typo introduced on 20 November is finally corrected to uint256.

    The regression propagates. ConsenSys/Tokens follows it on the same day at 13:24:54Z with “Added unapprove”, and the wiki follows it on 16 December with “update to latest Token standard draft”. For five weeks the specification, the reference implementation and the wiki all agree on an interface that no longer exists.

    Sources

  2. 2015-12-03
    2015-12-03 The Foundation's token tutorial Alex Van de Sande's “Ethereum in practice part 1” is the Foundation's canonical token tutorial. It links a gist that is character-for-character identical to the source MistCoin was compiled from, and it never uses the words ERC-20, ERC20, EIP-20 or “token standard”. Alex Van de Sandeblog.ethereum.orggist 21935dc…

    Full title: “Ethereum in practice part 1: how to build your own cryptocurrency without touching a line of code”. Published two weeks after issue #20 was filed.

    The post contains no inline Solidity. It instructs the reader to load the contract into the browser-based compiler from a gist: chriseth.github.io/browser-solidity/?gist=21935dc37c5bfbe92e5a. Its only description of what the token implements is:

    Since all tokens implement some basic features in a standard way, this also means that your token will be instantly compatible with the ethereum wallet and any other client or contract that uses the same standards.Verbatim

    It also concedes the wallet's discovery limitation directly: “the wallet only tracks tokens it knows about, and you have to add these manually.”

    What the linked gist contains

    contract MyToken {
        event Transfer(address indexed from, address indexed to, uint256 value);
        function MyToken(uint256 _supply, string _name, string _symbol, uint8 _decimals)
        function transfer(address _to, uint256 _value)
    }

    One method and one event. As of December 2015 the Ethereum Foundation's own public tutorial for creating a token neither named the standard nor implemented it.

    Gist 21935dc37c5bfbe92e5a, created 2015-12-01T18:37:25Z, is character-for-character identical to frozeman's gist revision 7bcfaef3, the revision MistCoin was compiled from. Verified by diff, and independently by compilation: built with solc 0.1.6+commit.d41f8b7c with the optimizer on, the blog post's gist produces MistCoin's exact 716-byte runtime and its exact 1,150-byte creation prefix. The token contract the Foundation published to the world on 3 December 2015 is, byte for byte, the contract deployed as MistCoin on 3 November.

    The post left a measurable trace onchain

    The tutorial tells the reader to enter “10,000 as the supply, any name you want, "%" for a symbol and 2 decimal places.” That instruction is visible in the constructor arguments of contracts deployed afterwards.

    DeployednConstructor symbol is "%"
    2015-11-03 to 2015-12-02211 (5%)
    2015-12-03 to 2016-01-069742 (43%)

    Weekly MyToken-shaped deployments

    Contracts with the exact MyToken shape, by week
    week of 2015-11-02:  17   (Wallet 0.3.5 ships)
    week of 2015-11-09:  10
    week of 2015-11-16:   2
    week of 2015-11-23:   2
    week of 2015-11-30:  36   (blog post, 2015-12-03)
    week of 2015-12-07:  33
    week of 2015-12-14:  17
    week of 2015-12-21:  10
    week of 2015-12-28:   8
    week of 2016-01-04:   5

    Two spikes, both attributable: eight deployments on 3 November, the day Wallet 0.3.5 shipped, and fifteen each on 4 and 5 December, the two days after the blog post. From the week of 23 November to the week of 30 November is an eighteenfold jump.

    Caveat on the "%" signal. The Wallet 0.3.5 release notes of 3 November also suggested "%" as a symbol, with three decimals rather than two. The rise in its frequency therefore measures readership, not novelty. The correlation between the post and the deployment spike is strong and the mechanism is plausible, but this is correlational evidence from timing, not proof of causation.

    Two circulating argument layouts

    LayoutConstructorPublished in
    rev≤4(uint256 _supply, string _name, uint8 _decimals, string _symbol)anonymous gist 909d02… of 2015-10-30, linked by Wallet 0.3.5; frozeman gist revisions 1 to 4
    rev5(uint256 _supply, string _name, string _symbol, uint8 _decimals)frozeman gist revision 7bcfaef3 of 2015-11-03; blog gist 21935dc… of 2015-12-01

    Of the 118 window deployments whose constructor arguments decode cleanly, 94 carry the rev5 layout and 24 the rev≤4 layout. The two published sources are distinguishable in the deployed bytes, in roughly a four-to-one ratio favouring the one the blog post carried.

    Sources

  3. 2015-12-1408:55:58Z
    2015-12-14 · 08:55:58Z · block 689,715 The only contract in the window with totalSupply SubEthaNomic, symbol SEN, at 0xCc0eE510BC4b5CD4D31Da49f672AB5aa6806F70a. Across 2,941 contracts with runtime bytecode in the drafting window, exactly one implements totalSupply(). mainnet3 of 6

    It carries balanceOf, totalSupply, transfer, name, symbol and the Transfer event. No allowance machinery at all. It is one of only ten contracts in the window that reach three of the six, and the only one of the ten that is not part of the 9-contract prototype cluster from early November.

    Sources

    • Raw artifactraw/onchain/window-token-vocabulary-hits.json
  4. 2015-12-2114:28:42Z
    15:55:57Z
    2015-12-21 · 14:28:42Z and 15:55:57Z “Derp. Approve is not a noun.” Two commits ninety minutes apart. The first deletes unapprove and renames the event Approve. The second fixes the name to Approval. The result is the first publicly available file containing exactly the ERC-20 interface and nothing else, sixteen days before the specification says the same thing. Simon de la RouviereConsenSys/Tokensfinding 3
    UTCSHAChange
    14:28:42Z9b89829“Absolute approval”. unapprove() deleted, event renamed Approve.
    15:55:57Zc3a3426“Derp. Approve is not a noun.” Approve becomes Approval.

    “Absolute approval” is the decision that removes unapprove: once approve overwrites the allowance rather than adding to it, setting it to zero is unapproving, and a separate method is redundant. That is the design argument the specification adopts on 6 January. The code makes it first.

    Token.sol at c3a3426, complete

    ConsenSys/Tokens Token_Contracts/contracts/Token.sol, verbatim, whole file
    contract Token {
    
        function totalSupply() constant returns (uint256 supply) {}
        function balanceOf(address _owner) constant returns (uint256 balance) {}
        function transfer(address _to, uint256 _value) returns (bool success) {}
        function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {}
        function approve(address _spender, uint256 _value) returns (bool success) {}
        function allowance(address _owner, address _spender) constant returns (uint256 remaining) {}
    
        event Transfer(address indexed _from, address indexed _to, uint256 _value);
        event Approval(address indexed _owner, address indexed _spender, uint256 _value);
    }

    This is finding 3. Every declaration matches the final standard, and there is nothing else in the file. The word ERC does not appear in it, in the commit message, or anywhere in the repository at this date.

    Sources

  5. 2015-12-2319:36:12Z
    2015-12-23 · 19:36:12Z ethereum.org drops sendCoin and adopts transfer Commit message “add the DAO page”. The token contract is replaced with the MyToken shape: balanceOf, transfer, event Transfer, plus name, symbol and decimals. The link to the wiki standard is removed and not replaced. Alexandre Van de Sandeethereum/ethereum-org2 of 6

    Two days after ConsenSys reached the exact interface, and fourteen days before the specification froze, the Foundation's own token page moves off the Frontier vocabulary. It does not move to the specification. It moves to the contract from the December blog post, which is the contract deployed as MistCoin.

    ethereum-org views/content/token.md at b0abb37, contract opening
    contract MyToken { 
        /* Public variables of the token */
        string public name;
        string public symbol;
        uint8 public decimals;
    
        /* This creates an array with all balances */
        mapping (address => uint256) public balanceOf;
    
        /* This generates a public event on the blockchain that will notify clients */
        event Transfer(address indexed from, address indexed to, uint256 value);
    
        /* Initializes contract with initial supply tokens to the creator of the contract */
        function myToken(uint256 initialSupply, string tokenName, uint8 decimalUnits, string tokenSymbol) {

    Two of the six required methods, one of the two required events, three of three optional members: the same score as MistCoin, and the same shape the wallet rendered. The Transfer event is indexed here, which the Frontier Guide's CoinTransfer never was.

    The same commit removes the “Meta coin standard” link to Standardized_Contract_APIs that had been on the page since July. From this date ethereum.org's token page points at no standard at all.

    Sources

    • Commitethereum-org b0abb37 · add the DAO page
    • Author date2015-12-23 17:36:12 -0200
    • Link removal“Meta coin standard” added 33b1217 (2015-07-15), removed b0abb37 (2015-12-23). No other commit touches the string.
  6. 2015-12-2910:55:42Z
    2015-12-29 · 10:55:42Z The DAO is still running the superseded interface Eight days after ConsenSys shipped the exact final interface, the largest token contract of the era carries one of the six, plus six members that had already been removed from the specification. CJentzschslockit/DAO1 of 6
    slockit/DAO Token.sol at 95d85c6
    function transfer(uint _value, address _to)
    function transferFrom(address _from, uint _value, address _to)
    function balanceOf(address _addr)
    function approve(address _addr)
    function unapprove(address _addr)
    function isApprovedFor(address _target, address _proxy)
    function approveOnce(address _addr, uint256 _maxValue)
    function isApprovedOnceFor(address _target, address _proxy)
    
    event Transfer(address indexed from, address indexed to, uint256 value)
    event AddressApproval(address indexed addr, address indexed proxy, bool result)
    event AddressApprovalOnce(address indexed addr, address indexed proxy, uint256 value)

    This is the October wiki, not the December issue. transfer still takes value before recipient, a form the wiki had already corrected two months earlier, on 28 October. The DAO adopts the exact final interface on 16 January 2016, and two days after that ConsenSys imports the DAO contract's NatSpec back into its own.

    Sources

  7. 2016-01-0108:56:36Z
    2016-01-01 · 08:56:36Z A README calls issue #20 “de facto finalised” The first README in any code repository to point at the issue. It appears five days before the issue body actually reaches its final form. Simon de la RouviereConsenSys/Tokens
    It follows the cutting edge standards (which is de facto finalised by the community here: https://github.com/ethereum/EIPs/issues/20)ConsenSys/Tokens README at bbbff96, verbatim

    The claim is true of the code and premature about the document. ConsenSys/Tokens had held the exact interface since 21 December. The issue body still said unapprove, Approved and Unapproved, and would for another five days.

    Sources

  8. 2016-01-0610:28:48Z
    2016-01-06 · 10:28:48Z The specification reaches its final form Revision 15. approve becomes absolute, unapprove and Unapproved are deleted, Approved is renamed back to Approval. Six methods, two events, nothing extra. Forty-eight days after the issue was filed. frozemanethereum/EIPs #20revision 152,351 bytes

    This is finding 4, and it is a bound rather than a timestamp. The issue thread was silent between 2015-12-02T10:22:08Z and 2016-01-06T10:12:13Z, so no snapshot exists inside that window. What the evidence supports exactly: the final interface was not present at 10:12:13Z and was present at 10:28:48Z, sixteen minutes and thirty-five seconds later.

    The body, verbatim, complete

    2,351 bytes. Carried by the comment at 2016-01-06T10:28:48Z.
    ```
    ERC: 20
    Title: Token standard
    Status: Draft
    Type: Informational
    Created: 19-11.2015
    Resolution: https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs
    ```
    # Abstract
    
    The following describes standard functions a token contract can implement.
    
    # Motivation
    
    Those will allow dapps and wallets to handle tokens across multiple interfaces/dapps.
    
    The most important here are, `transfer`, `balanceOf`, `decimals` and the `Transfer` event.
    
    # Specification
    
    ## Token
    
    ### Methods
    
    
    #### totalSupply
    
    ```js
    function totalSupply() constant returns (uint256 supply)
    ```
    Get the total token supply
    
    #### balanceOf
    
    ```js
    function balanceOf(address _owner) constant returns (uint256 balance)
    ```
    Get the account balance of another account with address `_owner`
    
    #### transfer
    
    ```js
    function transfer(address _to, uint256 _value) returns (bool success)
    ```
    Send `_value` amount of tokens to address `_to`
    
    #### transferFrom
    
    ```js
    function transferFrom(address _from, address _to, uint256 _value) returns (bool success)
    ```
    Send `_value` amount of tokens from address `_from` to address `_to`
    
    The `transferFrom` method is used for a withdraw workflow, allowing contracts to send tokens on your behalf, for example to "deposit" to a contract address and/or to charge fees in sub-currencies; the command should fail unless the `_from` account has deliberately authorized the sender of the message via some mechanism; we propose these standardized APIs for approval:
    
    #### approve
    
    ```js
    function approve(address _spender, uint256 _value) returns (bool success)
    ```
    Allow _spender to withdraw from your account, multiple times, up to the _value amount. If this function is called again it overwrites the current allowance with _value.
    
    #### allowance
    
    ```js
    function allowance(address _owner, address _spender) constant returns (uint256 remaining)
    ```
    Returns the amount which `_spender ` is still allowed to withdraw from `_owner`
    
    
    ### Events
    #### Transfer
    
    ```js
    event Transfer(address indexed _from, address indexed _to, uint256 _value)
    ```
    Triggered when tokens are transferred.
    
    #### Approval
    
    ```js
    event Approval(address indexed _owner, address indexed _spender, uint256 _value)
    ```
    Triggered whenever `approve(address _spender, uint256 _value)` is called.

    Three details are worth noting in that text. The Motivation still recommends decimals, which was removed six weeks earlier and is not defined anywhere below. The word “coins” from the original has been replaced by “tokens” throughout. And the sentence defining approve as overwriting rather than accumulating, “If this function is called again it overwrites the current allowance with _value”, is the sentence that makes unapprove unnecessary and is the direct descendant of ConsenSys's “Absolute approval” commit sixteen days earlier.

    What happened next, the same morning

    • 10:48:26Z, twenty minutes later, caktux mirrors it on the wiki: “update to latest draft - approve() now absolute, remove unapprove() and Unapproved(), Approved() renamed to Approval()”.
    • 20:55:55Z, revision 16, a twelve-byte change. The body then does not change again across 111 consecutive comment snapshots, until 28 October 2016. That is genuine stability, not a gap in coverage.

    Sources

  9. 2016-01-1000:21:08Z
    2016-01-10 · 00:21:08Z · block 824,235 First contract on mainnet carrying all six selectors 0x99146Bab2bB34D9Ca49EC4f0c82De3E5789ae22e, 6,701 bytes. Four days after the specification stopped moving. It carries the interface and is not a token: its supply reads zero at every block checked and it has never emitted a Transfer. mainnetfinding 8
    Address
    0x99146Bab2bB34D9Ca49EC4f0c82De3E5789ae22e
    Deployed
    2016-01-10T00:21:08Z, block 824,235
    Deployer
    0x4f53269e422711d4725F7381444c7F66F7D05788
    Runtime size
    6,701 bytes
    Contents
    All six methods. Both the Transfer and Approval topics. Plus owner() and 45 further unrecognised selectors.
    Supply
    totalSupply() returns zero at the block after deployment, five thousand blocks later, forty thousand blocks later, and at block 913,198. It has never emitted a Transfer.

    Carrying the interface and being a token are different claims, and this contract separates them. It is the Digix gold ledger, and this deployment and the one that follows it eleven hours later, 0xB2dd0dc22c7D103928650AbD260935Ef9EF40CFc, are staging runs: the same 6,701 bytes, no supply, no transfer, ever. The ledger that went into service is the third, on 14 January.

    The gap this closes is the central empirical result of the reconstruction. Between 3 November 2015 and 6 January 2016, across 2,941 contracts with runtime bytecode, nothing implements more than three of the six, and nothing at all implements approve or allowance. The allowance model, the half of ERC-20 that makes exchanges possible and the half that consumed almost all of the argument on the issue, has zero deployed instances during the period in which it was being specified.

    See the onchain section for the full census and the adoption curve.

    Sources

    • Raw artifactraw/onchain/y2016-final-six-4plus.json
    • DetectionOpcode walk over the runtime blob, cross-checked by substring match. See Method.
  10. 2016-01-1415:22:33Z
    2016-01-14 · 15:22:33Z · block 847,527 The first contract with the interface and a real supply 0x55b9a11c2e8351b4Ffc7b11561148bfaC9977855, Digix Gold 1.0, 6,770 bytes. The first contract that is a token and not only an interface: a supply exists from the block after it was deployed. It is not fully compliant with EIP-20, and the defects are in the event shape. mainnetfinding 9
    Address
    0x55b9a11c2e8351b4Ffc7b11561148bfaC9977855
    Deployed
    2016-01-14T15:22:33Z, block 847,527
    Deployer
    0x4f53269e422711d4725F7381444c7F66F7D05788
    Runtime size
    6,770 bytes
    Supply
    1,400,331,016,000
    First Transfer
    block 937,821

    Four tests separate a token from a contract that merely exposes the interface, and this is the first deployment to pass all four. It carries the six selectors, among 38 other functions. It has a real supply: totalSupply() returns 1,400,331,016,000 at block 847,528, the block after deployment, and the same value at every block sampled between there and block 937,821, so it was never an empty ledger waiting to be filled. It was meant as a token, being the address Digix names in its own gold-tokens-interface repository and the one MyEtherWallet's token list carries as DGX 1.0. And it emits Transfer when tokens move, from block 937,821 onward.

    The two Digix ledgers deployed four days earlier hold the identical interface and fail every test but the first. Their supply reads zero at deployment, five thousand blocks later, forty thousand blocks later, and at block 913,198, and neither has emitted a Transfer in its life. They are staging deployments of the contract this one became.

    What it is not is fully compliant with EIP-20. Run on a mainnet fork it breaks two mandatory requirements, both visible in what it emits:

    • Event shapeTransfer is declared with _value indexed. Every one it has emitted carries four topics and an empty data field. EIP-20 requires three topics with the value in data, so a reader following the standard finds no amount at all.
    • Wrong sendertransferFrom names the spender as _from, not the owner whose balance moved. One call emits three Transfer events, one of them a fee leg, and the movement leg attributes the debit to the wrong account.

    Both are properties of the deployed runtime, unchanged since 2016 and reproducible at any block. The first contract that satisfies every EIP-20 requirement arrives on 20 March 2016, sixty-six days later.

    The first ERC-20 token cannot be verified on Etherscan. It belongs to a Digix system compiled by solc v0.1.7 commit c806b9bc, a build that exists in the Solidity history and was never published to solc-bin, so no released compiler reproduces its bytecode.

    Sources

  11. 2016-01-1614:34:37Z
    2016-01-16 · 14:34:37Z The DAO adopts the exact interface “update Token.sol to new standard”. Six of six, no extras. Two days later ConsenSys imports this contract's NatSpec into its own. CJentzschslockit/DAO6 of 6
    function totalSupply()
    function balanceOf(address _owner)
    function transfer(address _to, uint256 _value)
    function transferFrom(address _from, address _to, uint256 _value)
    function approve(address _spender, uint256 _value)
    function allowance(address _owner, address _spender)
    
    event Transfer(address indexed _from, address indexed _to, uint256 _value)
    event Approval(address indexed _owner, address indexed _spender, uint256 _value)

    Documentation flows in the opposite direction to the interface. The interface went from the issue to ConsenSys to the DAO. The NatSpec goes from the DAO back to ConsenSys, on 18 January, with the commit message “Added NATSPEC from Christoph Jentzsch's Slock.it Token Contract.”

    Sources

  12. 2016-01-2500:39:07Z
    2016-01-25 · 00:39:07Z “implement ERC 20”, the first commit message to name the standard A single-line change to a source comment. It is the moment the reference implementation stopped citing the wiki page and started citing the issue. The code does not change. ethersConsenSys/Tokensfinding: first naming
    ConsenSys/Tokens 85610c3, complete diff
    diff --git a/Token_Contracts/contracts/Standard_Token.sol b/Token_Contracts/contracts/Standard_Token.sol
    index ab28fea..28f1ceb 100644
    --- a/Token_Contracts/contracts/Standard_Token.sol
    +++ b/Token_Contracts/contracts/Standard_Token.sol
    @@ -1,7 +1,7 @@
     /*Most, basic default, standardised Token contract.
     Allows the creation of a token with a finite issued amount to the creator.
     
    -Based on standardised APIs: https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs
    +Implements ERC 20 Token standard: https://github.com/ethereum/EIPs/issues/20
     .*/
     
     import "Token";

    Two months after the issue was filed and five weeks after the code was already correct, someone writes the standard's name down in a repository for the first time. The name lags the thing by a wide margin at every stage of this trail.

    The naming sequence

    UTCWhatWhereText
    2016-01-01T08:56:36ZFirst README pointing at issue #20ConsenSys/Tokens bbbff96“de facto finalised by the community here”
    2016-01-25T00:39:07ZFirst commit message naming ERC 20ConsenSys/Tokens 85610c3implement ERC 20
    2016-02-08T20:06:34ZFirst closed-up form “ERC20”ConsenSys/Tokens aba060b“Commented out TransferFrom to reflect current uncertainty in ERC20.”
    2016-04-16T19:14:35ZWiki commit naming ERC20ethereum/wiki f3f6a74“further highlight ERC20”
    2016-04-24T19:55:53ZFirst repo named erc20; first Solidity contract ERC20dapphub/erc20 e970781erc20 type definition

    “First” here means first across the sources enumerated in Method. Commit-message evidence outside those repositories was sampled through GH Archive rather than exhaustively searched.

    Sources

  13. 2016-01-2716:54:44Z
    2016-01-27 · 16:54:44Z · block 913,198 The first ERC-20 transfer elcoin, 0xa04bf47F0E9D1745D254b9B89f304c7d7ad121Aa. One account pays another 1,000,000 units, two hours after the contract was deployed. The first time a token moves under this interface. The contract itself is not compliant: its ERC-20 entry points return false and do nothing. mainnetfinding 10
    Address
    0xa04bf47F0E9D1745D254b9B89f304c7d7ad121Aa
    Deployed
    2016-01-27T14:47:57Z, block 912,760
    First Transfer
    2016-01-27T16:54:44Z, block 913,198
    From
    0x48175Da4c20313bcb6B62d74937d3fF985885701
    To
    0x96CB25A6445648A56352677D6C80600F769F2642
    Value
    1,000,000

    Not a mint. The sender is the deployer's own account, not the zero address, so this is one holder paying another rather than a supply being created. The contract answers name() with a bytes32 that decodes to elcoin, and answers neither symbol() nor decimals(), both of which were optional and arrived later.

    Thirteen days separate the first token from the first transfer, and in that window no contract carrying the interface moved a single token. Digix Gold, deployed first, did not emit its own first Transfer until block 937,821.

    The event is real, the interface behind it is not. elcoin holds all six selectors, but its own ERC-20 entry points do nothing. Called on a fork, transfer, approve and transferFrom each return false, move no balance and emit no event, from a funded holder as readily as from an empty account. The contract asks a security registry at 0xa95b9127e7102dCFa3869c47ee12a0Ec85C261C5 whether the caller is permitted, receives zero, and returns. The behaviour is identical at block 950,000 and at the head of the chain, so this is not a later configuration change.

    Every transfer elcoin recorded, all 1,450 of them, was driven through a platform controller rather than through transfer. The chain also records the other half of that: every direct call to transfer(address,uint256) in elcoin's history failed. It is an asset on a permissioned platform that exposes the ERC-20 shape, not a contract that behaves as ERC-20 requires.

    Sources

    • DetectionLog scan on topic ddf252ad… from each candidate's deployment block, earliest first.
  14. 2016-01-2814:01:48Z
    2016-01-28 · 14:01:48Z · block 917,622 The first minimal ERC-20 on mainnet 0x37Dca38b1CBB2Cd043910eC46fe82Ddb9e38F00d, 754 bytes. A dispatcher containing the six selectors and nothing else. A real token, with a supply of 1,000,000 that has never moved. It meets nine of the ten requirements of EIP-20 as finalised, and the tenth was written after it was deployed. mainnetfinding 11
    Address
    0x37Dca38b1CBB2Cd043910eC46fe82Ddb9e38F00d
    Deployed
    2016-01-28T14:01:48Z, block 917,622
    Deployer
    0x16893e10b99A59afd2C60331E0B49241d4d4d7cC
    Runtime size
    754 bytes
    Contents
    Exactly totalSupply, balanceOf, transfer, transferFrom, approve, allowance. Both event topics. Zero unrecognised selectors. No name, symbol or decimals.
    Supply
    1,000,000, all of it the deployer's
    Transfer events
    None, ever

    It is a token, not a bare interface. totalSupply() returns 1,000,000 and balanceOf of the deployer returns the same, so the whole supply sits where it was created and has never moved: the contract has not emitted a single Transfer. The source Etherscan serves for it has no constructor, which is what makes the supply look impossible. Etherscan verifies against the deployed runtime, and constructor code never reaches the runtime, so a verified source can be missing the constructor that set the contract's opening state. The chain is the record, not the source.

    This is the deployed counterpart of ConsenSys's c3a3426: the interface and nothing else, on mainnet, thirty-eight days after the same shape appeared in a file. Only 28 contracts across January and February 2016 combined carry all six.

    It is one requirement short of full compliance, and the requirement did not exist yet. Executed against all ten obligations of EIP-20 as finalised, it satisfies nine. Both transfer and transferFrom are guarded by && _value > 0, so a transfer of zero returns false and emits nothing, where the finished standard requires it to be treated as an ordinary transfer and to fire Transfer.

    That clause is worth dating. It appears in the earliest copy of eip-20-token-standard.md in the EIPs repository, on 13 July 2017. It is not in the text of issue #20 as filed on 19 November 2015, and it is not in the issue today. Judged against EIP-20 as it was finally written, this contract fails. Judged against the specification as it stood on the day it was deployed, it passes, and it is the first contract that does.

    Everything else holds: all three mutators return a 32-byte boolean, approve overwrites rather than accumulates, transferFrom decrements the allowance, and both events carry three topics with the value in data. It is also the only one of the early candidates with verified source on Etherscan, so the guard can be read rather than inferred.

    Sources

    • Raw artifactraw/onchain/y2016-final-six-4plus.json
8 February 2016 – 13 December 2016

Adopting the name

The interface has stopped changing. What happens through 2016 is that the phrase “ERC20” detaches from the issue number and becomes the name of a thing, while the wiki page that started it stops specifying anything and defers.

  1. 2016-02-0820:06:34Z
    2016-02-08 · 20:06:34Z The closed-up form: “ERC20” “Commented out TransferFrom to reflect current uncertainty in ERC20.” The first use of the name without a space, two weeks after the first use with one. Simon de la RouviereConsenSys/Tokens

    The commit message is also evidence that the standard was not settled in practice even after the text froze. A TransferFrom event had been added on 30 January and is commented out nine days later, explicitly because of “current uncertainty”.

    Sources

  2. 2016-02-1219:22:49Z
    2016-02-12 · 19:22:49Z approve and transferFrom reach ethereum.org The allowance model appears on the Foundation's token page for the first time, five weeks after the specification froze and three months after it was written into issue #20. Alexandre Van de Sandeethereum/ethereum-org

    Commit message: “Added improve this button. Removed old images”. The page roughly doubles in size, from 14.8 KB to 36.1 KB, and gains an advanced token section carrying approve and transferFrom.

    The ordering across the Foundation's own properties is the point. approve and allowance entered the specification on 19 and 20 November 2015. They reach ethereum.org on 12 February 2016. The go-ethereum wiki tutorial behind the Frontier Guide never gains them at all.

    Even here the page does not name the standard. The word ERC does not appear.

    Sources

  3. 2016-02-1318:38:56Z
    2016-02-13 · 18:38:56Z Van de Sande's own implementation Gist alexvandesande/0d1a998d949e26942212, “Token Standard”. Five of six by signature, missing totalSupply, and no Approval event. Its approve calls back into the spender. alexvandesandegist 0d1a998…5 of 6
    contract tokenRecipient { function sendApproval(address _from, uint256 _value, address _token); }
    
    contract MyToken {
        string public name; string public symbol; uint8 public decimals;
        mapping (address => uint256) public balanceOf;
        mapping (address => mapping (address => uint)) public allowance;
        mapping (address => mapping (address => uint)) public spentAllowance;
        event Transfer(address indexed from, address indexed to, uint256 value);
    
        function transfer(address _to, uint256 _value) { … }
        function approve(address _spender, uint256 _value) returns (bool success) {
            allowance[msg.sender][_spender] = _value;
            tokenRecipient spender = tokenRecipient(_spender);
            spender.sendApproval(msg.sender, _value, this);
        }
        function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { … }
        function () { throw; }
    }

    allowance is present as a public-mapping getter rather than a declared function, which produces the same selector. The spentAllowance mapping means approve here sets a cumulative cap rather than the remaining allowance, which is not the final semantics.

    The tokenRecipient.sendApproval callback, renamed receiveApproval in his next comment three days later, is the direct ancestor of the approveAndCall and receiveApproval pattern that shipped in the ethereum.org token tutorial and propagated into thousands of 2017 token contracts. It is not part of ERC-20.

    Sources

  4. 2016-03-1513:28:32Z
    2016-03-15 · 13:28:32Z “All your token need is balanceOf and transfer” Four months after MistCoin and two months after the specification froze, the Foundation's UX lead is still telling token authors on the standard's own issue thread that two methods and one event are sufficient. alexvandesandeethereum/EIPs #20comment 196816918
    In order to work on the Ethereum Wallet all your token need is to implement correctly balanceOf and transfer and their corresponding events. We are waiting for all others but the latest proposed standard is kept updated at ethereum.org/tokenVerbatim

    This is the single most useful sentence in the whole issue for dating what “compliance” meant. It is stated by the person who built the client that defined the requirement, on the standard's own thread, and it corroborates the wallet ABI evidence from the other direction. See Compliance.

    His other interventions on the thread

    UTCCommentSubstance
    2015-11-19T16:31:57Z158110210Defends decimals, name, symbol; states the “pave the cowpaths” doctrine
    2015-11-30T19:13:48Z160729345Proposes int256 over uint256 so tokens can represent debt. Rejected.
    2015-12-02T16:00:32Z161344667Continues the signed-balance argument
    2016-02-13T18:42:35Z183720260Posts an implementation with approve plus a recipient callback
    2016-02-16T12:18:18Z
    2016-02-16T12:24:17Z
    184661092, 184662479Argues approve should always notify the spender; proposes bytes metadata
    2016-02-17T12:39:09Z185181532Had originally wanted hooks on transfer too; was talked out of it
    2016-03-01T18:17:32Z
    2016-03-01T22:23:11Z
    190839646, 190933561Refers to the member as decimalPlaces() and to baseUnit as prior art
    2016-03-15T13:28:32Z196816918The operative statement of what compliance meant
    2016-04-12
    2016-04-15
    2016-04-19
    208944198, 210397271, 211868211transfer and balanceOf are settled, approve is not; asks for a standard version variable

    Sources

    • Raw artifactraw/gharchive-events/issue20-events.jsonl
  5. 2016-03-2011:09:16Z
    2016-03-20 · 11:09:16Z · block 1,184,107 The first fully ERC-20 compliant contract 0xacFD9D15fA769EaBb68410c4c675Ff2030f26416, 2,356 bytes. An ether wrapper. The first contract on mainnet that satisfies all ten requirements of EIP-20 as finalised when its own code is executed. It was never used. mainnetfinding 12
    Address
    0xacFD9D15fA769EaBb68410c4c675Ff2030f26416
    Deployed
    2016-03-20T11:09:16Z, block 1,184,107
    Deployer
    0x3a77633f26ddD2cf58D1fea2C889098565cA5C8C
    Runtime size
    2,356 bytes
    Contents
    The six required methods, both event topics, plus deposit() and withdraw(uint256). No name, symbol or decimals.
    Use
    One transaction in its life, its own creation. Zero logs.

    It is an ether wrapper, the pattern later made familiar by WETH. deposit() credits the caller with the ether sent, withdraw(uint256) returns it, and totalSupply() reports the contract's own ether balance rather than a stored figure, so supply and backing cannot drift apart. It emits the same Deposit and Withdrawal topics that canonical WETH9 would later use.

    Compliance here is a claim about behaviour, so it was tested by behaviour. The contract was run on a mainnet fork, funded through its own deposit() rather than by writing storage, and every obligation checked against real transactions: receipts read for the events actually emitted, eth_call used to measure the width of each return value. It passes all ten, and passes them twice, once at the head of the chain and once on a fork pinned 93 blocks after its deployment under the hardfork rules of the day.

    Reaching this point took a single opcode. The contract deployed on 25 February, 0xb345180D0a2c791d4943a239f8eBb50eFA01C81a, is the same code for its first 2,342 bytes and differs only in the tail bounds helper, which compares with ADD GT where this one compares with ADD LT ISZERO. Strict against inclusive. Under the strict form an amount of zero fails the check and the call throws, which is why the February contract rejects zero-value transfers and this one accepts them. That one comparison is the whole twenty-three day gap.

    Two things this claim does not say. It does not say this contract has name, symbol or decimals, because it has none. Those three are optional in EIP-20 and their absence does not affect compliance, but anyone who reads “fully compliant” as including them will want a later contract. Unknown selectors on this contract fall through to deposit(), so name() appears to answer; that is the fallback, not a metadata function. And it does not say this contract was important. It was never used. The address that carried this code into service is its byte-identical twin 0xd654bDD32FC99471455e86C2E7f7D7b6437e9179, deployed 81 blocks later the same morning by the same account, which went on to record 806 logs and 363 transfers from June 2016.

    Everything deployed before it fails. All 34 contracts carrying the six selectors that predate this block were executed the same way and every one breaks at least one requirement: thirteen return no boolean from transfer, four return no data at all from totalSupply() and balanceOf(), three index _value in Transfer, one has ERC-20 entry points that do nothing, and the remaining thirteen reject transfers of zero, some of them emitting the wrong events besides.

    Sources

    • CorpusEvery create trace of 2015 and 2016, 6,187 and 230,818 respectively, internal creates included. 2015 yields no contract with all six selectors; 2016 yields 1,014.
    • DetectionPUSH-aware disassembly for selector presence, then execution on an anvil mainnet fork for behaviour. Balances obtained through each contract's own mint path where one exists, otherwise by impersonating a live holder.
  6. 2016-04-2419:55:53Z
    2016-04-24 · 19:55:53Z The first repository named erc20 Nikolai Mushegian commits “erc20 type definition”. It is the first repository whose entire purpose is the interface, and the first Solidity file named erc20.sol declaring contract ERC20. Nikolai Mushegiandapphub/erc206 of 6 by selector

    Cloned and read from the initial commit. README.md in full: “dapple package for ERC20 token type interface”. dappfile declares name: erc20.

    contracts/erc20.sol at that commit, complete

    contract ERC20 {
        function totalSupply() constant returns (uint);
        function balanceOf(address who) constant returns (uint);
        function allowance(address owner, address spender) constant returns (uint);
    
        function transfer(address to, uint value) returns (bool ok);
        function transferFrom(address from, address to, uint value) returns (bool ok);
        function approve(address spender, uint value) returns (bool ok);
    
        event Transfer(address indexed from, address indexed to, uint value);
        event Approval(address indexed owner, address indexed spender, uint value);
    }

    Six methods, two events, nothing else. Scored by literal declaration text against the final signatures this reads as three of six, because uint and uint256 are different strings. They are the same ABI type. Scored by canonical selector, which is what interoperability actually depends on, it is six of six and both events match:

    totalSupply()                          0x18160ddd
    balanceOf(address)                     0x70a08231
    allowance(address,address)             0xdd62ed3e
    transfer(address,uint256)              0xa9059cbb
    transferFrom(address,address,uint256)  0x23b872dd
    approve(address,uint256)               0x095ea7b3
    Transfer(address,address,uint256)      0xddf252ad…b3ef
    Approval(address,address,uint256)      0x8c5be1e5…b925
    Two qualifications on “first repo named erc20”. The repository was originally under the nexusdev organisation, not dapphub, visible in the merge commit 7e8cb92 of 2016-08-08: “Merge branch 'master' of github.com:nexusdev/erc20”. And “first” remains scoped to a gh search repos sweep over 2015 and 2016 by name and description. It is not a claim about every repository that has ever existed.

    The base implementation arrives sixteen minutes later in c0b1dfc, “copy base implementation and tests from dappsys”. The repository keeps its purpose: fc53f48 of 2017-02-02 reduces it back to the interface, and the file survives through solc 0.5.0 and 0.6.6.

    No repository created in 2015 has ERC20 in its name or description

    CreatedRepositoryDescription
    2016-02-22daifoundation/maker-otcThe OasisDEX protocol, simple onchain market for ERC20 tokens
    2016-04-24dapphub/erc20erc20 interface definition container package
    2016-05-11nexusdev/token-freezerMulti-tenant ERC-20 token locker
    2016-08-04BangkitSedar/ERC20-Token-StandardERC20 Token Standard
    2016-08-10dapphub/ds-eth-tokenERC20 ETH token wrapper
    2016-08-15dapphub/ds-tokenA simple and sufficient ERC20 implementation
    2016-11-08Giveth/minimeMiniMe Token. ERC20 compatible clonable token
    2016-12-02danfinlay/human-standard-token-abiA JSON ABI for the Ethereum ERC 20 Token Standard

    A search for “token standard” restricted to Solidity over the same two years returns zero results.

    Sources

  7. 2016-05-2020:38:26Z
    2016-05-20 · 20:38:26Z The wiki stops specifying the interface “Transferable Fungibles is ERC 20”. Revision 46 replaces the interface section with a pointer to the issue. The page that started the standard eleven months earlier stops describing it. ethersethereum/wikirevision 46

    Revisions 46 onward no longer specify anything. From this date the issue is the only document that defines ERC-20, which is also the reason its edit history had to be reconstructed from event payloads.

    ConsenSys makes the same separation a week earlier, on 13 May: StandardToken.sol is made ERC20-only, and HumanStandardToken.sol is created to carry name, symbol and decimals. The optional three finally get their own file.

    Sources

  8. 2016-11-0209:47:51Z
    2016-11-29
    2016-11-02 · 09:47:51Z, and 2016-11-29 · 09:49:56Z totalSupply is briefly dropped, then restored Revision 18 removes totalSupply from the body. Revision 19, twenty-seven days later, restores it as returns (uint256 totalSupply). That is the text still live on the issue. frozemanethereum/EIPs #20revisions 18 & 19

    Between revision 16 on 6 January 2016 and revision 17 on 1 November 2016 the body did not change across 111 consecutive comment snapshots. The three late revisions are the only movement in the text after the standard froze.

    The last of the nineteen recovered revisions, on 2016-11-29, is the body as it stands. Two weeks after that, on 13 December 2016, frozeman marks his original proposal gist outdated with a banner pointing at the issue.

    Sources

7 December 2016 – 29 September 2017

Formal adoption

The events that turn a GitHub issue into a numbered, merged, Final standard. The first three fall outside the corpus this page is built from and are marked as such, with dashed markers, so that nothing here is mistaken for evidence gathered in the same way as everything above. The fourth is from the ethereum.org clone and carries its commit.

  1. 2016-12-07
    2016-12-07 An “ERC” category is created in EIP-1 Hudson Jameson adds ERC as a category to the EIP process document, giving the label the standard had been using informally for thirteen months an official definition. Not evidenced by this corpus. outside the corpusno artifact held
    No artifact for this event is held in the collection behind this page. The reconstruction covers ethereum/EIPs issue #20, the wiki, the gists, the implementation repositories and the onchain corpus, through late 2016. It contains no EIP-1 revision history and no pull request data. The date and description above are supplied context, not a finding, and carry no citation because there is no primary source here to cite.

    Note the ordering it implies, which the rest of this page does support: the term “ERC” was in use in a commit message from 25 January 2016, in a repository name from 24 April 2016, and in the issue's own header block from 19 November 2015. The category is defined last.

  2. 2017-04-24
    2017-04-24 Fabian Vogelsteller submits the standard as a pull request Pull request #610 against ethereum/EIPs, moving the text out of the issue and into a file in the repository. Seventeen months after the issue was opened. Not evidenced by this corpus. outside the corpusno artifact held
    No artifact for this event is held in the collection behind this page. The corpus holds 285 GH Archive events for issue #20 and 479 for the repository as a whole, all from 2015 and 2016, plus the comment index. It holds no pull request records. The date and number above are supplied context, not a finding.

    What this page does establish about the same interval is that nothing in the text changed after 29 November 2016, so whatever was submitted in April 2017 was a document that had been stable for months.

  3. 2017-09-11
    2017-09-11 Merged as Final EIP-20 reaches Final status, one year and ten months after issue #20 was opened, and one year and eight months after the interface it describes stopped changing. Not evidenced by this corpus. outside the corpusno artifact held
    No artifact for this event is held in the collection behind this page. The date above is supplied context, not a finding.

    Placed against the dated findings on this page, the interval is the point. The specification's text froze on 6 January 2016. The first mainnet contract carrying the six selectors appeared on 10 January 2016, and the first that was actually a token on 14 January 2016. A thousand and fourteen contracts carried all six methods before the end of 2016. The formal status arrived after all of that.

  4. 2017-09-2921:22:55Z
    2017-09-29 · 21:22:55Z The word ERC20 reaches ethereum.org, as a filename Commit “Separate solidity files” moves the inline contracts into solidity/, one of them named token-erc20.sol. It is the first and only occurrence of the string in the repository, eighteen days after EIP-20 became Final. Alex Van de Sandeethereum/ethereum-orgfirst naming

    Searching the whole repository across all 1,210 commits from 2015-03-07 to 2019-04-17:

    StringFirst commit that introduces it
    ERC20298cb09, 2017-09-29, in the filename solidity/token-erc20.sol
    ERC-20never
    ERC 20never
    EIPs/issues/20never

    And in views/content/token.md itself, the page a reader actually saw, the substring ERC never appears in any of its 117 revisions, through the last commit in the repository.

    The Ethereum Foundation's own token page never named the standard in prose. It linked the wiki as the “Meta coin standard” from July to December 2015, then linked nothing. The only place ERC20 is written anywhere on the site is a Solidity filename, added eighteen days after EIP-20 reached Final status on 11 September 2017.

    That closes the loop with the Frontier Guide. Neither of the Foundation's two documentation properties adopted the name while the standard was being written, and only one of them ever adopted the interface.

    Sources

Method by method

When each member appeared

Seven of the eight members are renames of something older. One, totalSupply, is newly invented. The two that define the standard are the last to arrive.

Table scrolls sideways →

MemberFirst public appearance (UTC)AuthorArtifactReplaced
balanceOf2015-10-04T15:07:06ZGav Woodethereum/wiki 607b6accoinBalanceOf
Transfer (event)2015-10-04T15:07:06ZGav WoodSame commitCoinTransfer
transfer2015-10-06T12:57:06ZSimon de la Rouviereethereum/wiki bfc39cbsendCoin
transferFrom2015-10-06T12:57:30ZSimon de la Rouviereethereum/wiki 9721a6bsendCoinFrom, via the typo trasnferFrom 24 seconds earlier
final transfer / transferFrom signatures2015-10-28T13:44:47ZFabian Vogelstellerethereum/wiki 0627f24Parameter order reversed
totalSupply2015-11-18T08:59:46Zfrozemangist e7abcddNewly invented
approve(address,uint256)2015-11-19T19:07:35ZfrozemanIssue #20 revision 5approve(address)
allowance2015-11-20T15:53:42ZfrozemanIssue #20 revision 7isApprovedFor
Approval (event)2015-11-20T15:53:42ZfrozemanIssue #20 revision 7AddressApproval

Members that existed and were removed

MemberPresent fromRemovedWhere
decimals()2015-11-19T09:52:56Z, issue #20 revision 12015-11-26T10:34:22Z, revision 11Voted out on the wiki poll
unapprove()Wiki 2015-09-02, as disapprove from 2015-08-242016-01-06 in the issue, 2015-12-21 in codeRemoved when approve became absolute
approveOnce / isApprovedOnceForWiki 2015-06-18 and 2015-08-242015-11-19T19:07:35Z, revision 5Merged into a capped approve
isApprovedWiki 2015-06-18Wiki 2015-09-06
AddressApproval, AddressApprovalOnceWiki 2015-08-24Issue #20 revision 7Replaced by Approval
Approved, UnapprovedIssue #20 revision 13, 2015-12-022016-01-06Short-lived regression
The elapsed time from the first final name (balanceOf, 4 October 2015) to the last (allowance and Approval, 20 November 2015) is 47 days. The elapsed time from the creation of the wiki page to the last is 5 months and 3 days. Almost all of the interface's design happened in a seven-week span, and the decisive part of it in about thirty hours.

Side by side

What each source implemented

Every document and contract in the trail, scored against the finished standard. Read down a column to watch a member arrive. Read across a row to see how far any one artifact got.

Table scrolls sideways →

UTCArtifact totalSupplybalanceOftransfertransferFromapproveallowance TransferApproval Score
2015-06-17Wiki 748c9b0, as created~0/6
2015-05-04Frontier Guide tutorial, as created0/6
2015-07-24Frontier Guide tutorial, final form0/6
2015-09-06dapp-bin currency.sol~0/6
2015-10-04Wiki 607b6ac, Gav Wood~1/6
2015-10-06Wiki 9721a6b, after the typo fix~~~1/6
2015-10-28Wiki 0627f24, parameter order fixed~3/6
2015-11-03MistCoin, deployed bytecode~2/6
2015-11-17Token proposal gist v1~3/6
2015-11-18Token proposal gist v4~4/6
2015-11-19Issue #20 revision 1, as posted~4/6
2015-11-19Issue #20 revision 55/6
2015-11-20Issue #20 revision 7, all eight co-exist6/6
2015-11-30ConsenSys/Tokens 4ba2396, first compilable6/6
2015-12-01Blog post gist 21935dc…~2/6
2015-12-02Issue #20 revision 13, the regression6/6
2015-12-21ConsenSys/Tokens c3a3426, exactly ERC-206/6
2015-12-29slockit/DAO 95d85c6~~~~1/6
2016-01-06Issue #20 revision 15, final form6/6
2016-01-16slockit/DAO bf27cf76/6
2016-02-13Van de Sande gist 0d1a998…5/6
2016-04-24dapphub/erc20 e970781, by selector6/6

The two Frontier Guide rows score zero across the board and are included for exactly that reason: the official documentation for the first live Ethereum network shares no member, under any signature, with the standard that was being written beside it. Its coinBalanceOf matches the wiki's superseded getter, not balanceOf.

Two notes on scoring. The Score column counts only the six required methods at their final signatures, which is why revision 13 scores 6/6 despite having renamed the Approval event away. ~ against approve before 19 November 2015 means the member exists but takes no amount. MistCoin's transfer is marked ~ because it shares the final selector but returns nothing, where the standard specifies returns (bool success). Cosmetic differences in return variable names are treated as equivalent, since they do not change the ABI.

Deployed 3 November 2015

MistCoin

Deployed 3 November 2015 at 12:03:29Z, sixteen days before issue #20 was filed. What the deployed bytecode contains, where the source came from, and where the contract sits in the record.

Contract
0xf4eCEd2f682CE333f96f2D8966C613DeD8fC95DD
Deployed
2015-11-03T12:03:29Z, block 483,325
Transaction
0x74349ce6…54a7
Deployer
0x9b22a80D5c7B3374a05b446081f97d0A34079e7F
Name, symbol
MistCoin, MC
Supply
100,000,000 raw units at 2 decimals, so 1,000,000 MC
Runtime
716 bytes
Creation payload
1,406 bytes = 1,150 code + 256 bytes of ABI-encoded constructor arguments

What the deployed bytecode contains

Decoded by walking the dispatcher. The complete set of PUSH4 operands in the 716-byte runtime is five, and the complete set of PUSH32 operands is one. Not a sample. All of them.

0x06fdde03  name()
0x313ce567  decimals()
0x70a08231  balanceOf(address)
0x95d89b41  symbol()
0xa9059cbb  transfer(address,uint256)

0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
            Transfer(address,address,uint256)

Against the finished standard

EIP-20 memberRequired?In MistCoin
totalSupply()required
balanceOf(address)required
transfer(address,uint256)required✔ *
transferFrom(address,address,uint256)required
approve(address,uint256)required
allowance(address,address)required
Transfer(address,address,uint256)required
Approval(address,address,uint256)required
name()optional
symbol()optional
decimals()optional

* Same selector, but the deployed transfer returns nothing. EIP-20 specifies returns (bool success). The dispatcher pushes return address 0x0045, which is a JUMPDEST immediately followed by STOP.

Score: 2 of 6 required methods, 1 of 2 required events, 3 of 3 optional members.

Bytecode-verified provenance

The source is not inferred from resemblance. It was recompiled with a period-correct compiler and matched byte for byte.

Source
Gist frozeman/20c8b5658349b003b08d revision 7bcfaef3be689c81f8ee20ddb190031efe8fc110, committed 2015-11-03T12:03:46Z
Compiler
solc 0.1.6-d41f8b7c/.-Emscripten/clang/int (soljson-v0.1.6+commit.d41f8b7.js)
Settings
Optimizer enabled
Runtime
716 bytes, exact match to the deployed runtime
Creation
1,150 bytes, exact prefix match to the deployed creation payload, with exactly 256 bytes of constructor arguments appended

The previous gist revision, 759cddeb of 11:59:12Z, produces the same runtime but a different creation prefix: it diverges at byte 41, in the constructor's argument handling. Decoding MistCoin's 256-byte argument tail settles which one was deployed.

MistCoin creation payload, constructor argument tail, decoded
[0] 0x…05f5e100   uint256 _supply   = 100,000,000
[1] 0x…00000080   offset -> "MistCoin"
[2] 0x…000000c0   offset -> "MC"
[3] 0x…00000002   uint8   _decimals = 2

The order is (_supply, _name, _symbol, _decimals). Revision 759cddeb declares (_supply, _name, _decimals, _symbol). MistCoin was compiled from revision 7bcfaef3, the revision saved seventeen seconds after the block that contains the deployment.

Where it sits in the record

Earlier contracts with the core three

First occurrenceUTCContract
balanceOf(address)2015-08-08T16:05:56Z
block 54,180
0x5fC8AeFc86884f0792995c015ff12647fafA0d83
transfer(address,uint256)2015-08-19T09:36:20Z
block 110,635
0x65c4E65113DB14f8c15702883791cCA9E66C5Ed2
Transfer event2015-10-23T09:41:56Z
block 426,661
0x3C655ccb35666579511489af88153517fc58b017
All three together2015-10-23T09:41:56Z
block 426,661
0x3C655ccb35666579511489af88153517fc58b017

Measured across every contract created on mainnet from Frontier launch to 2015-12-31: 6,187 create traces, 5,724 with runtime bytecode, 628 carrying token vocabulary. By that measure MistCoin is the tenth contract to carry balanceOf, transfer and the Transfer event together, eleven days after the first.

The nine earlier ones are not nine independent tokens. They are a prototyping run by two addresses over eight days, detailed in the timeline entry for 23 October 2015. Not one of the nine has name, symbol or decimals. They had a balance and a transfer. None had anything a wallet could label.

What it is first at

MistCoin is the first contract on Ethereum mainnet carrying the complete shape the Ethereum Wallet rendered: balanceOf, transfer and Transfer, and name, symbol and decimals. It is number one of 130 such contracts in 2015. The second arrives 6 hours 24 minutes later.

MistCoin is the first token contract that the official Ethereum Wallet could display. Not the first contract with transfer, and not an implementation of a standard that did not yet exist. The first that the wallet could render with a name, a symbol and a decimal point, on the day the wallet gained the ability to render one.

It is also, per an index of 12,023,046 contracts, the earliest of 173 contracts across all of Ethereum history sharing its exact runtime bytecode. It is the genesis member of the MyToken family, which is the largest single artifact of the 2015 tutorial.

Who made it

MistCoin was deployed by Fabian Vogelsteller, from the address 0x9b22a80D5c7B3374a05b446081f97d0A34079e7F. He wrote the MyToken gist it was compiled from that morning, and he authored and published Ethereum Wallet 0.3.5, the release that gave the wallet its token feature, later the same day. He has described the deployment publicly since.

Its first transfer sent half the supply to Alex Van de Sande. Both were at the Ethereum Foundation: Vogelsteller built the wallet and Mist, Van de Sande led its user experience. A month later Van de Sande wrote the Foundation's tutorial on creating a token, “Ethereum in practice part 1”, which shipped this contract's source to a general audience.

Sources

What the chain shows that week

The deployment was the end of a short run of rehearsals, and the traces show the shape of the work rather than a single moment.

  • In all of 2015 the MistCoin address deployed exactly two contracts carrying token vocabulary: MistCoin itself, and 0xE274d18EF7b194A1EDEbB04cfE297CFe1489ef65 on 26 October, the same pattern rehearsed eight days earlier.
  • That rehearsal is byte-identical to a contract deployed two minutes and twelve seconds later by 0xB1a2B43A7433dd150BB82227eD519Cd6b142d382, which was running a twelve-contract prototyping campaign across six bytecode families over the same weeks. Two addresses putting the same build on mainnet two minutes apart.
  • The final deployment sits seventeen seconds before the gist revision it was compiled from, and one hour forty minutes before the wallet release that shipped the feature.

Read together with the gist and release history, this is a client feature and its first example token being finished on the same morning, hours before the release went out.

What the standard was that day

On 3 November 2015 the standard was the wiki page, and issue #20 did not exist. The wiki had held the names balanceOf and transfer for thirty and twenty-eight days respectively, and their final parameter order for six days, so MistCoin's two methods do match the page as it stood.

What the page also specified, and MistCoin does not implement, is the entire rest of it: transferFrom, and five approval members, approve, unapprove, isApprovedFor, approveOnce and isApprovedOnceFor, plus the events AddressApproval and AddressApprovalOnce. There was no totalSupply on the page yet, no allowance and no Approval. Those three arrive on 18, 20 and 20 November. MistCoin implements the two members of the October wiki that a wallet needed, and none of the eight it did not.

This is a fact about the calendar rather than about the contract. A token deployed sixteen days before the proposal was written cannot implement the proposal. What it did do was establish the shape the wallet rendered, which a few hundred contracts copied over the following two months.

3 November 2015 to 6 January 2016

What was deployed

This is the empirical test the documentary trail cannot supply. While the standard was being written, what went on mainnet? Every create trace in the window was collected and every runtime blob decoded.

3,062contracts created in the window
2,941with runtime bytecode
320carrying any token vocabulary
3highest count of the six reached by any of them

Coverage of the final six

Members presentContracts
082
183
2145
310
40
50
60

No contract deployed to Ethereum mainnet between 3 November 2015 and 6 January 2016 implements more than three of the six ERC-20 methods.

Frequency of each member

MemberContractsShare of the 320
balanceOf(address)207
transfer(address,uint256)185
transferFrom(…)10
totalSupply()1
approve(address,uint256)0
allowance(address,address)0
Nothing in the window emits Approval. Nothing in the window implements approve(address,uint256) or allowance(address,address). The allowance model, the half of ERC-20 that makes exchanges and later DeFi possible, and the half that consumed almost all of the argument on issue #20, has zero deployed instances during the period in which it was being specified.

The optional three, and the events

Member or topicContracts
name()172
symbol()142
decimals()140
Transfer(address,address,uint256)154
CoinTransfer(address,address,uint256)53
CoinSent(address,uint256,address)2
AddressApproval(address,address,bool)1
AddressApprovalOnce(address,address,uint256)1
Approval(address,address,uint256)0

The three members the standard marks optional are implemented more often than four of the six it requires.

The superseded vocabulary

Superseded memberContracts
coinBalanceOf(address)56
approve(address)12
approveOnce(address,uint256)12
isApprovedOnceFor(address,address)12
isApprovedFor(address,address)10
unapprove(address)10
currency()8
balances(address)6
coinBalance(), disapprove, isApproved, sendCoin, sendCoinFrom2 each
transfer(uint256,address), transferFrom(address,uint256,address)1 each

coinBalanceOf, which Gav Wood renamed away on the wiki on 4 October 2015, was still being deployed 56 times through December. 53 of those 56 pair it with the CoinTransfer event and carry no sendCoin under the specification's signature: that is the shape of the contract in the official Frontier Guide tutorial, which never changed. The count of 2 for sendCoin reflects the scan's vocabulary, not the deployments; see the note below.

The sendCoin row understates its subject. The scan's superseded-vocabulary list was built from the wiki, dapp-bin and the DAO, so it tested sendCoin(uint256,address), selector 0xc86a90fe. The Ethereum Frontier Guide taught sendCoin(address receiver, uint amount), selector 0x90b98a11, which was never tested. Across all of 2015, 264 of the 628 token-vocabulary contracts carry coinBalanceOf together with the CoinTransfer topic, the guide's exact pairing, and only 7 of those also carry the specification's sendCoin. The dominant token contract on mainnet in 2015 was not written against the standard being drafted. It was written against the documentation, and the documentation had its own vocabulary.

A sample of what was on mainnet

Of the 320, 140 have the exact MyToken shape: balanceOf, transfer, name, symbol, decimals and Transfer, and nothing else. 118 yield a clean constructor decode. The first row of that census, sorted by block, is MistCoin.

Deployed (UTC)AddressNameSymbol
2015-11-03T12:03:29Z0xf4eCEd2f682CE333f96f2D8966C613DeD8fC95DDMistCoinMC
2015-11-03T18:46:18Z0x796Ed7f47E100984e7aA7ca51D55e9B68eCb1C19WhitcoinWHIT
2015-11-03T22:17:12Z0xDafE447177aEfb05Dd7eDAe2F5781c98A858d320UniCoinUNC
2015-11-03T23:23:36Z0xaB3652FD492FbB0d6b63acB742f3eD12AfBAEf52BizilicasBiZ
2015-11-04T08:54:38Z0xEFB1775952642353c0386410212D7638c9fB2426EtherMusicETM
2015-11-08T23:29:00Z0xA1162CBb7F6cc8F8476c5f4783761302a9aBaf69Ethereum UnitETH
2015-11-12T16:41:26Z0x896BA935dfBe3c5dDFBc1b637bE60964e5244465CannabisTokenCANN
2015-11-16T16:46:40Z0x3B683F1ba138A094042e368415d8B9fEF86731A4CoinAwesomeAWE
2015-12-04T17:38:17Z0xE671b8Acf6aCD77Ec885EfA7e3C93bE05E887407My DAO Shares%
2015-12-14T08:55:58Z0xCc0eE510BC4b5CD4D31Da49f672AB5aa6806F70aSubEthaNomicSEN
2016-01-06T16:13:52Z0x5A2EbC3AC433fd6c9Ad2B1a56033Dc2D45945315pieshopdollar$

Not one of these is an ERC-20 token by the finished standard. All of them were, at the time, tokens, because the wallet said so.

When the six appear

The same scan run over all of 2016: 230,818 create traces, 213,112 with runtime bytecode.

Contracts in 2016Count
Four or more of the final six2,295
All six1,014
All six plus both final event topics554

Contracts with all six, by month

2016-01:   11        2016-07:  101
2016-02:   17        2016-08:  126
2016-03:   19        2016-09:  162
2016-04:   35        2016-10:  115
2016-05:   34        2016-11:  154
2016-06:   91        2016-12:  149

The answer to “does any token from 2015 or 2016 implement all six?” is: none in 2015, and 1,014 in 2016, the first on 10 January 2016, four days after the text stopped moving.

Adoption of the specification as written is a 2016 phenomenon that only reaches triple digits per month in June 2016, seven months after issue #20 was filed. Only 28 contracts across January and February 2016 combined carry all six.

A methodological caveat, stated because it bit. The opcode walk desynchronises on contracts that embed non-code data in the runtime blob. For the 6,701-byte contract of 10 January it recovered the Approval topic but missed Transfer, which is unambiguously present. Every headline count was therefore re-run with plain substring matching over the runtime hex, which cannot desynchronise.

Two detectors, in exact agreement

MemberOpcode walkSubstring
totalSupply()11
balanceOf(address)207207
transfer(address,uint256)185185
transferFrom(address,address,uint256)1010
approve(address,uint256)00
allowance(address,address)00
Transfer topic154154
Approval topic00

Zero contracts where the substring test found a member the walk missed. The four-byte substring test is the looser of the two: it produces false positives, not false negatives. A zero from both methods is as strong as this corpus can make it. The claim that nothing in the window implements approve or allowance does not depend on the choice of detector.

Four definitions

What compliance meant

Four readings are in circulation, and they date differently. The corpus lets each be dated precisely. The first three are answerable from documents and bytecode. The fourth is answerable only by running the code.

1. As the wallet defined it

balanceOf, transfer, Transfer. This is the operative definition for the whole period. It is what the Ethereum Wallet's ABI required from 23 October 2015, what Alex Van de Sande told the issue thread as late as 15 March 2016, and what 154 of the 320 token-vocabulary contracts in the window implement.

By this definition MistCoin complies, along with nine earlier contracts and about 150 later ones. It has nothing to do with EIP-20's required set.

2. As the specification defines it

All six methods, both events. First stated in prose on 2015-11-20T15:53:42Z. First expressed exactly, with nothing extra, on 2015-12-21T15:55:57Z in ConsenSys/Tokens. First stated exactly by the specification itself on 2016-01-06.

Zero contracts in the window meet it. Zero implement even four of the six. approve and allowance have no deployed instances at all in the window, and Approval is emitted by nothing.

3. As applied retroactively

The reading under which a 2015 contract “is” ERC-20 because ERC-20 later described something like it. This is not a property of the contract. It is a property of the reader.

It is also unfalsifiable, which is why it is worth naming rather than arguing with.

4. As EIP-20 finally required it

Not the selectors but the behaviour: the return values, the event shape, the allowance arithmetic, and transfers of zero. Definition 2 can be answered from bytecode. This one cannot, because a function that returns a boolean and one that returns nothing share a selector, and indexing _value does not change an event's topic hash.

First met on 2016-03-20T11:09:16Z, block 1,184,107, by 0xacFD9D15fA769EaBb68410c4c675Ff2030f26416. Nothing in 2015 meets it, and neither does any of the 34 qualifying contracts deployed before that block.

The gap between the first two definitions is the whole finding. The standard was written by people who were, at the same time, shipping a client that did not require it. The wallet's tokenInterface.js wanted name, symbol and decimals, none of which ERC-20 requires, and did not want totalSupply, transferFrom, approve or allowance, four of the six that it does.

The accurate statement about any 2015 token, MistCoin included, is that it meets the first definition and cannot meet the second, because the second did not exist yet. Nothing deployed in the window complies with it. The first contracts that do are a 2016 phenomenon.

One note on how compliance is scored on this page. Text-identity and ABI-identity are different tests, and only the second determines whether two contracts interoperate. dapphub/erc20 reads as three of six by literal declaration text, because uint and uint256 are different strings, and six of six by canonical selector. The selector count is the one that matters.

The selector count settles definition 2 and stops there. It cannot settle definition 4, so for that question every candidate was executed.

Definition 4, tested by execution

Every contract deployed in 2015 or 2016 whose dispatcher carries all six selectors was collected, 1,014 of them, all in 2016. All 34 deployed before the first one to pass were then run on an anvil mainnet fork and checked against all ten obligations of EIP-20 as finalised: real transactions, receipts read for the logs actually emitted, eth_call used to measure return width. The milestones of that pass, in order:

Table scrolls sideways →

Deployed (UTC)ContractDeploys the interfaceInterface and a real supplyFully compliantWhat breaks
2016-01-10
824,235
0x99146Bab2bB34D9Ca49EC4f0c82De3E5789ae22e
Digix gold ledger
Supply is zero at every block checked, so it is an interface and not a token. Transfer indexes _value; transferFrom reports the spender as _from.
2016-01-14
847,527
0x55b9a11c2e8351b4Ffc7b11561148bfaC9977855
Digix Gold 1.0
Event shape. Transfer indexes _value, so it emits four topics and an empty data field. transferFrom names the spender as _from rather than the owner.
2016-01-27
912,760
0xa04bf47F0E9D1745D254b9B89f304c7d7ad121Aa
elcoin
The entry points do nothing. transfer, approve and transferFrom return false, move no balance and emit no event. Its transfers were driven through a controller.
2016-01-28
917,622
0x37Dca38b1CBB2Cd043910eC46fe82Ddb9e38F00d ~ Zero-value transfers only. Nine of ten. A guard of && _value > 0 makes a transfer of zero return false and emit nothing. The earliest copy of that requirement is dated July 2017 and it is absent from issue #20, so it post-dates this contract.
2016-02-25
1,059,698
0xb345180D0a2c791d4943a239f8eBb50eFA01C81a ~ The same code as the row below except one comparison, ADD GT against ADD LT ISZERO. Under the strict form a transfer of zero throws.
2016-03-20
1,184,107
0xacFD9D15fA769EaBb68410c4c675Ff2030f26416
ether wrapper
Nothing. All ten. Verified at the head of the chain and on a fork pinned 93 blocks after deployment. Never used: one transaction, its creation. Its twin 0xd654bDD32FC99471455e86C2E7f7D7b6437e9179, 81 blocks later, is the one that saw traffic.

The middle two columns are bytecode and state questions and were answered as such. The third is a behavioural question and was answered by execution. A contract can hold every selector, hold a real supply, and still not behave the way the standard requires, which is what the first three rows are.

One requirement carries a date of its own. “Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event” is in EIP-20 as finalised and in the earliest copy of eip-20-token-standard.md in the EIPs repository, dated 13 July 2017. It is not in issue #20 as filed on 19 November 2015, and it is not in the issue today. It is also the only requirement separating 0xacFD9D15fA769EaBb68410c4c675Ff2030f26416 from candidates as much as seven weeks earlier. Applying it to a contract from early 2016 is applying a rule written after the fact, which is worth saying plainly rather than burying: by EIP-20 as finalised the answer is 20 March 2016; by the specification as it stood when these contracts were deployed the answer is 0x37Dca38b1CBB2Cd043910eC46fe82Ddb9e38F00d, 28 January 2016. Both dates are stated here, and neither is presented as the only one.

How the dating was done, and where it stops

Method, sources and limits

The problem with issue #20

GitHub did not record issue-body edit history until late 2016. GET /repos/ethereum/EIPs/issues/20 returns diff: null and serves only the current body. The document that defines ERC-20 has no visible history at the place it lives.

The recovery

Every IssueCommentEvent in the GitHub public event stream embeds the entire parent issue object, including issue.body as it stood at that instant. Issue #20 accumulated 284 comments between 2015-11-19 and 2016-12-22. Each is a dated snapshot of the body.

  1. Fetch all comments on the issue. 362 comments; 284 fall inside 2015-11 to 2016-12.
  2. Map each created_at to its GH Archive hour. 198 distinct hourly files.
  3. Download each. Zero missing or corrupt files, verified with gzip -t.
  4. Filter for the repository and issue number. 285 events: one IssuesEvent:opened and 284 IssueCommentEvent.
  5. De-duplicate payload.issue.body in timestamp order. 19 distinct revisions.

Eight of these were previously known. Eleven are recovered here for the first time.

Reproducing the recovery
# 1. every comment on issue 20
gh api --paginate repos/ethereum/EIPs/issues/20/comments \
  -q '.[]|[.created_at,(.id|tostring),.user.login,.html_url]|@tsv' > comments.tsv

# 2. the distinct GH Archive hours those comments fall in
awk -F'\t' '{gsub("T","-",$1); split($1,a,"-");
             printf "%s-%s-%s-%d\n",a[1],a[2],a[3],a[4]+0}' comments.tsv | sort -u > hours.txt

# 3. fetch them
xargs -P 8 -I{} curl -s -o {}.json.gz http://data.gharchive.org/{}.json.gz < hours.txt

# 4. pull the embedded issue body out of every event
#    (write per-file, never to a shared stdout: parallel writes interleave
#     and corrupt the JSON)
for f in *.json.gz; do gzcat "$f" | grep -a 'ethereum/EIPs' > "parts/${f%.json.gz}.jsonl"; done
cat parts/*.jsonl | jq -c 'select((.payload.issue.number//0)==20)' \
  | jq -r '[.created_at,((.payload.issue.body//"")|@base64)]|@tsv' | sort | uniq -f0

2015-era GH Archive rows store payload as an object in these files, but BigQuery's githubarchive.day.2015* tables store it as a JSON string. JSON_EXTRACT_SCALAR is required there, not dot access.

Sources examined

SourceAccessCoverage
ethereum/EIPs issue #20GH Archive + GitHub API19 body revisions, 2015-11-19 to 2016-11-29
ethereum/wiki Standardized_Contract_APIsgit cloneAll 54 revisions, 2015-06-17 to 2018-08-22
ethereum/wiki poll pagegit clone9 revisions, 2015-11-21 to 2015-11-26
gist frozeman/090ae32…gist history API6 revisions
gist frozeman/20c8b56…gist history API5 revisions
ConsenSys/Tokensfull clone2015-07-15 to present
slockit/DAOfull cloneToken.sol from 2015-12-29
ethereum/meteor-dapp-walletfull clonetokenABI.js 2015-10-06 to 2015-12-02
ethereum/mistfull clone, releases APIRelease 0.3.5 metadata and body
ethereum/dapp-binfull clonestandardized_contract_apis/currency.sol
ethereum/frontier-guidefull cloneAll 216 commits, 2015-04-30 to 2015-07-28
go-ethereum.wikifull cloneContract-Tutorial.md all 29 revisions, plus Coin-Contract-Tutorial.md
ethereum/ethereum-orgfull cloneAll 1,210 commits, 2015-03-07 to 2019-04-17. views/content/token.md all 117 revisions
dapphub/erc20full cloneFrom 2016-04-24
blog.ethereum.orgfetched“Ethereum in practice part 1” by Alex Van de Sande, 2015-12-03
r/ethereumlinkedThe Wallet 0.3.5 announcement thread, and Fabian Vogelsteller's account of MistCoin
GitHub repo searchgh search reposAll repos created 2015-01-01 to 2016-12-31
gist alexvandesande/0d1a998…gist history API2 revisions, from 2016-02-13
gist 909d02…, anonymousgist history API1 revision, 2015-10-30
Mainnet create traceslocal BigQuery export2015-07-30 to 2016-12-31. 6,187 (2015) and 230,818 (2016) traces with runtime bytecode
Local contract indexSQLite, 12,023,046 contractsIndependent cross-check of the create-trace counts and bytecode families
solc 0.1.x archivelocal soljson buildsPeriod-correct recompilation

Onchain detection

For each runtime blob the EVM opcode stream is walked and every PUSH4 and PUSH32 operand collected. Walking the opcode stream rather than substring-matching avoids false positives from data bytes that happen to spell a selector. PUSH4 operands are matched against the keccak-256 selectors of the final six, the optional three and 20 superseded members; PUSH32 operands against 8 candidate event topic hashes.

def push_operands(code_hex):
    b = bytes.fromhex(code_hex[2:]); p4=set(); p32=set(); i=0
    while i < len(b):
        op=b[i]; i+=1
        if 0x60 <= op <= 0x7f:                # PUSH1..PUSH32
            ln=op-0x5f; data=b[i:i+ln]; i+=ln
            if ln==4:  p4.add(data.hex())
            if ln==32: p32.add(data.hex())
    return p4, p32

Limits of this evidence

  1. Issue-body snapshots exist only where a comment landed. The thread was silent between 2015-12-02T10:22:08Z and 2016-01-06T10:12:13Z, so an intermediate edit inside that window is invisible. The correct statement of finding 4 is a bound: the exact interface was present at 10:28:48Z and was not present at 10:12:13Z.
  2. A long stable period is genuine, not a gap. Between 2016-01-06T20:55:55Z and 2016-10-28T04:21:56Z the body did not change across 111 consecutive comment snapshots.
  3. “First public appearance” means first in the sources listed above. These are the canonical ones for this standard, but the claim is a lower bound on lateness, not proof of universal novelty. An exhaustive search would require the full 2015 and 2016 GH Archive corpus, roughly 8,760 files per year.
  4. Commit-message evidence for the term “ERC 20” was searched exhaustively within the cloned repositories and sampled through 280 GH Archive hours. An earlier use may exist in a repository not examined.
  5. Author dates, not committer dates, are used throughout. For these repositories the two agree on the commits cited.
  6. Onchain detection is bytecode-level, not semantic. A selector in a dispatcher proves a function is callable, not that it behaves as the standard specifies. The onchain section counts interfaces, not correctness.
  7. Create traces only. The onchain corpus contains contract creations and their bytecode. It holds no transaction logs, so no claim about transfers, balances or holders is testable from it.
  8. Onchain addresses are not identities. The corpus resolves deployments to addresses, not to people. Where a deployment is attributed to a named person on this page, the basis is the public record outside the corpus: gist and release authorship, and the participants' own accounts.
  9. Signature matching is mechanical. A ~ means the parameter list differs from the final form in order, type or arity. Differences in return variable names are treated as equivalent, since they do not change the ABI.
  10. A dispatcher scan can miss non-standard dispatchers. A contract using a hand-written dispatcher, or a proxy, would be missed. For 2015 Solidity output this is not a realistic concern, but it is a stated assumption rather than a proof.
  11. 121 of the 3,062 window contracts have no runtime bytecode and were not analysed. They are almost certainly failed constructors, but that is an assumption.
  12. The scan's vocabulary list is incomplete, and the Frontier Guide shows where. Its superseded members were drawn from the wiki, dapp-bin and the DAO, so it tested sendCoin(uint256,address) and not the guide's sendCoin(address,uint256), selector 0x90b98a11. Every count of sendCoin on this page is therefore a count of the specification's signature only, and understates how many contracts had a working transfer function. The affected contracts are identified by their coinBalanceOf and CoinTransfer pairing instead.

Gaps this pass did not close

MistCoin's first transfer is recorded outside this corpus. The onchain export holds contract-creation traces and bytecode only, with no transaction logs, so the transfer of half the supply to Alex Van de Sande is carried here on Fabian Vogelsteller's public account rather than on the traces. Reading it off the chain would need event-log data for blocks 483,325 and later.

The guide-shaped contracts have not been re-scanned. The 264 contracts identified by the coinBalanceOf plus CoinTransfer pairing are almost certainly compiled from the official tutorial, but the archived corpus stores decoded member lists rather than runtime bytecode, so sendCoin(address,uint256) could not be tested directly. Re-running the opcode walk over the 2015 and window create traces with 0x90b98a11 added to the vocabulary would convert the inference into a count.

The three formalisation events of December 2016 and 2017 have no artifact here. They are shown in the timeline's final era and marked as outside the corpus. Closing them needs the ethereum/EIPs pull request history and the EIP-1 revision history.