Etherscan API

Etherscan is a really useful website but fairly recently I discovered they also have a pretty handy Developer API. I used it to analyse gas a contract was using over a period of time and thought it might be useful to record.

Getting Started

First of all an API key is required and this can be created for free by logging in and going to: https://etherscan.io/myapikey.

I’m going to paste my code with comments which should be easy enough to follow. The API covers a whole lot of other end point such as Logs, Tokens, etc that I didn’t use but the docs are good and worth a scan.

Example

Photo by Markus Spiske on Unsplash

Buidler, Waffle & Ethers

Lately at Balancer we’ve moved from the Truffle development environment to using Buidler, Waffle and Ethers. The main benefit is being able to use console.log in Solidity during debugging – it’s amazing how much of a difference this makes and for this alone the change over is worth it. Here’s some notes I made during the switch over.

Ethers

The ethers.js library aims to be a complete and compact library for interacting with the Ethereum Blockchain and its ecosystem.

Documentation is here: https://docs.ethers.io and this Web3.js vs Ethers.js guide was useful.

The following gist demonstrates some basic usage of Ethers that creates an instance of a deployed contract and then running some calls against it:

Buidler & Waffle

Buidler is described as a ‘task runner’. I think its easiest to see it as a swap for Truffle/Ganache. It has lots of different plugins that make it really useful and its documentation was refreshingly good.

The Quickstart shows you how to install and how to run common tasks. It also uses Waffle for testing. Waffle is a simple smart contract testing library built on top of Ethers.js. Tests in Waffle are written using Mocha alongside with Chai and from my experience everything just worked. The docs are here. And its worth digging in to see some of the useful things it offers such as Chai Matchers which allow you to test things like reverts, events, etc.

Buidler commands I found I used a lot:

  • Run the local Buidler EVM: $ npx buidler node
  • Compile project contracts: $ npx buidler compile
  • Run tests: $ npx buidler test ./test/testfile.ts

Here’s an example test file I used that demonstrates a few useful things:

Static Calls

let poolAddr = await factory.callStatic.newBPool(); – The contract callStatic pretends that a call is not state-changing and returns the result. This does not actually change any state and is free.

Connecting Different Accounts

await _pools[1].connect(newUserSigner).approve(PROXY, MAX); – Using contract connect(signer) calls the contract via the signer specified.

Gas Costs

await proxy.connect(newUserSigner).exitswapExternAmountOut(
            _POOLS[1],
            MKR,
            amountOut,
            startingBptBalance,
            {
              gasPrice: 0
            }
        );

Setting the gasPrice to 0 like above allows me to run the transaction without spending any Eth on it. This was useful when checking Eth balance changes without having to worry about gas costs.

Custom accounts & balances

const config: BuidlerConfig = {
  solc: {
    version: "0.5.12",
    optimizer: {
      enabled: true,
      runs: 200,
    },
  },
  networks: {
    buidlerevm: {
      blockGasLimit: 20000000,
      accounts: [
        { privateKey: '0xPrefixedPrivateKey1', balance: '1000000000000000000000000000000' },
        { privateKey: '0xPrefixedPrivateKey2', balance: '1000000000000000000000000000000' }
      ]
    },
  },
};

I needed the test accounts to have more than the 1000Eth balance set by default. In buidler.config.ts you can add accounts with custom balances like above.

Deploying

Deploying is done using scripts. First I updated my buidler.config.ts with the account/key for Kovan that will be used to deploy (i.e. must have Eth):

const config: BuidlerConfig = {
  solc: {
    version: "0.5.12",
    optimizer: {
      enabled: true,
      runs: 200,
    },
  },
  networks: {
    buidlerevm: {
      blockGasLimit: 20000000,
    }
    kovan: {
      url: `https://kovan.infura.io/v3/${process.env.INFURA}`,
      accounts: [`${process.env.KEY}`]
    }
  },
};

Then I wrote a deploy-script.js:

async function main() {
  // We get the contract to deploy
  const ExchangeProxy = await ethers.getContractFactory("ExchangeProxy");
  const WETH = '0xd0A1E359811322d97991E03f863a0C30C2cF029C';
  const exchangeProxy = await ExchangeProxy.deploy(WETH);

  await exchangeProxy.deployed();

  console.log("Proxy deployed to:", exchangeProxy.address);
}

main()
  .then(() => process.exit(0))
  .catch(error => {
    console.error(error);
    process.exit(1);
  });

Then run this using: npx buidler run --network kovan deploy-script.js

🎉 Console Logging 🎉

One of the holy grails of Solidity development and so easy to setup in this case! There are also Solidity stack traces and error messages but unfortunately there was a bug that caused this not to work for our contracts.

To get this going all you need to do is add: import "@nomiclabs/buidler/console.sol"; at the top of your contract then use console.log. More details on what kind of outputs, etc it supports are here. Lifesaver!

Hope some of this was helpful and you enjoy using it as much as me.

(Photo by Kevin Jarrett on Unsplash)

🤓 Geeking Out On Uniswap 🦄

Uniswap is a simple smart contract interface for swapping ERC20 tokens and in general it’s pretty awesome. The story behind it is really inspirational to me and just recently Hayden Adams tweeted that $10million of liquidity was added in a 24 hour period – surely pretty succesful by any measure!

Some of the features of Uniswap include:

  • Supplies on-chain liquidity to other smart contracts
  • Ease of use
  • Gas efficiency (10x less than Bancor)
  • Decentralised/censorship resistant

And just as a reminder:

Liquidity describes the degree to which an asset or security can be quickly bought or sold in the market at a price reflecting its intrinsic value.

Investopedia

Each Uniswap pool holds Eth and another token and trades are exectuted against these reserves.

By supplying tokens to the pooled reserve (being a liquidity provider) you get a proportional share of transaction fees via a liquidity token.

Prices are set automatically using eqn: x * y = k or in terms of tokens: eth_pool * token_pool = invariant.

The invariant is constant during trades but DOES change when liquidity is added or removed from pool. (So not really an invariant?!)

Liquidity Tokens

Liquidity tokens are minted to track the relative proportion of total reserves that each liquidity provider has supplied.

Fees are taken during a token swap and are added to the liquidity reserves. Since total reserves are increased without adding any additional share tokens, this increases that value of all liquidity tokens equally. This functions as a payout to liquidity providers that can be collected by burning shares. (It’s also the reason that the invariant increases at the end of every trade)

When a liquidity provider joins the pool the amount of liquidity tokens minted are calcualted by:

(Initial pool liquidity is equal to initial Eth value provided)
total_liquidity = self.totalSupply
eth_reserve = self.balance - msg.value
liquidity_minted = msg.value * total_liquidity / eth_reserve

Liqudity tokens can be burned at any time to return a proportional share of the markets liquidity to the provider:

removeLiquidity(amount...):

total_liquidity = self.totalSupply
token_reserve = self.token.balanceOf(self)
eth_amount = amount * self.balance / total_liquidity
token_amount = amount * token_reserve / total_liquidity

self.balances[msg.sender] -= amount
self.totalSupply = total_liquidity - amount
send(msg.sender, eth_amount)
return eth_amount, token_amount

Exchanging

Eth -> Token: Traders Eth is added to pool and Token is removed. So Eth amount increases, token amount decreases. Token becomes more expensive.

Token -> Eth: Traders Token is added to pool and Eth is removed. So Eth amount decreases, token amound decreases. Eth becomes more expensive.

Arbitrage

Arbitrage seems to be the key to so many DeFi applications! I think the following is nice description of what it is:

Arbitrage trading is a strategy that can be best understood as a trader that takes advantage of the price differential that exists between two markets. In the case of cryptocurrency, this price differential can be found in the differences in price of a digital asset between cryptocurrency exchanges. If a trader identified an opportunity for arbitrage trading, then they would purchase a digital asset in one exchange, and then sell it on another cryptocurrency exchange.

Mycryptopedia

To me it’s all about icentives – basically in the form of greed! There’s always someone looking to make money from an opportunity. In this case they execute a trade to make a profit but by making they trade they change the price which basically corrects it to the market price – that’s cool!

For example:

  • the global price of ETH-USD moves enough away from the pool price, an arbitrage opportunity exists and is corrected
  • When the initial liquidity is provided the exchange rate is set. If the ratio of liquidity provided isn’t realistic arbitrage traders will correct at the expense of initial liquidity provider.

Some Examples With Numbers

Invariant is set on initial deposit to a new pool. For example 10 ETH and 500 FUN are deposited into new ETH/FUN pool. Invariant is set to:

ETH_pool * FUN_pool = invariant
10 * 500 = 5000

Now for an ETH -> FUN trade:

Buyer sends 1ETH

Fee = 1 ETH / 400 = 0.0025 ETH (0.25% fee)

ETH_pool = 10 + 1 - 0.0025 = 10.9975

FUN_pool = 5000/10.9975 = 454.65 (invariant/ETH_pool)

Buyer receives: 500 - 454.65 = 45.35 FUN

Fee is added back to pool:

ETH_pool = 10.9975 + 0.0025 = 11

FUN_pool = 454.65

New invariant = 11 * 454.65 = 5001.15

Executed price = 45.35 FUN/ETH

But now price has changed:

Fee = 0.0025 ETH
ETH_pool = 11.9975
FUN_pool = 5001.15/11.9975 = 416.85
Buyer receives: 454.65 - 416.85 = 37.8

Executed price = 37.8 FUN/ETH

Price Slippage

Price slippage refers to the difference between the expected price before a transaction is executed and the actual price at which it is executed.

Bancor Support

Easiest for me to think of spot price and actual price.

A trade that is large relative to the size of the total size of the liquidity pool will cause price slippage.

Same example as above: 
ETH_pool * FUN_pool = invariant
10 * 500 = 5000

Spot price for ETH -> FUN = 500/10 = 50

1ETH Purchase: Executed price = 45.35 FUN/ETH

10ETH Purchase: Executed price = 24.969 FUN/ETH

Fee = 10 ETH / 400 = 0.025 ETH (0.25% fee)

ETH_pool = 10 + 10 - 0.025 = 19.975

FUN_pool = 5000/19.975 = 250.31

Buyer receives: 500 - 250.31 = 249.69 FUN

Fee is added back to pool:

ETH_pool = 19.975 + 0.025 = 20

FUN_pool = 250.31

New invariant = 20 * 250.31 = 5006.2

Executed price = 24.969 FUN/ETH

Final Thought

I found this comment from Vitalik on this EthResearch post interesting:

The point is not for this kind of exchange to be the only exchange; the point is for it to be one type among many. It offers the benefits of executing a complete trade in one transaction, and extreme user-friendliness even to smart contracts, which are very real benefits and will at least sometimes exceed the costs of slippage to some users. I am ok with just accepting that this kind of approach will not be acceptable to whales who want to liquidate large amounts of things; that’s not the target market.

(A lot of this stuff came from the Uniswap White paper. Photo by James Lee on Unsplash)

Solidity – How To Estimate Gas Costs

I’ve been working on optimizing the gas cost for deploying a smart contract. Turns out truffle has some pretty useful ways of estimating gas used.

Good Old Migrate

The most obvious way is to just run $ truffle migrate and see what the output says. When the project has quite a large build this can take some time so it’s not that efficient for seeing the difference some code changes make. I do find it useful to run this every now and again though just to confirm the estimates are correct.

estimateGas()

The other way I found useful is to use the estimateGas function. As is pretty self explanatory it is just an estimate and could change depending on the contract state. I found this was nice and quick to use on a truffle cli and I found two useful estimations to look at:

Contract deployment

Start the local ganache with a large gas limit:

$ ganache-cli --gasLimit 10000000 --allowUnlimitedContractSize

Start a truffle cli that connects to above chain:

$ truffle console

Estimate gas for deploying your contract:

truffle(develop)> YourContract.new.estimateGas()

Calling Contract Method

Start with the same set-up steps as above. Then:

truffle(develop)>  YourContract.testMethod.estimateGas()

And that’s it – another useful trick!

Migrating SAI to DAI

I was using Dharma.io to earn interest on my SAI (formerly DAI). I found Dharma really nice to use and originally offered a good interest rate. Recently the rate had become less competitive and I was getting concerned (maybe unneccesarily?) about the lack of comms about migrating the SAI to DAI so I decided to take matters into my own hands especially now that the DAI Savings Rate has kicked in.

When I swapped I was getting 3.22% for SAI on Dharma.io. The DAI savings rate is 4% and looks like it might get raised to 6% soon so definitely worth the swap.

  1. Visit https://migrate.makerdao.com/
  2. Unlock SAI. This calls the approve function for the token. (Nice explanation here.)
  3. This had a gas cost of 0.002637ETH, $0.35.
  4. That was it – once I checked my account I could see DAI.
  5. Not to earn some interest.
  6. Click earn savings. This navigates to https://oasis.app/save.
  7. Deploy Proxy – Setting up your proxy will bundle multiple transactions into one, saving transaction time and gas costs. This only has to be done once.
  8. Gas cost: 0.007344ETH, $0.97
  9. Now wait for 10 confirmations.
  10. Approve Oasis for DAI.
  11. 0.00055ETH, $0.07
  12. Finally deposit the DAI.
  13. 0.002713ETH, $0.36

Total cost $1.75. And now it’s quite satisfying to watch my depost earn interest in real time!

(Photo by Fabian Blank on Unsplash)

🎉✨🔥 Winner Winner! 🔥✨🎉

Well this is cool – I won the Gitcoin x Aave Hackathon!

My entry was a bot that does arbitrage between two Uniswap exchanges using an Aave Flashloan as the capital for initial trade. The Aave judges were “super impressed” with my work and I got a special mention for the way I overcame a testnet problem by forking UniSwap and customising the code to allow two trading between two exchanges with the same token.

Happy days!

Note – Deploying A Contract Via A Delegate With Truffle Set-Up

This is quite random but I had to learn a few things so worth taking note.

I was recently working on a Truffle Dapp. I had to deploy one of my contracts in a roundabout way – basically one account signing it but another paying the gas (Metatransactions are basically the same). I’d never done this before. After that I still wanted my Truffle Dapp to be able to access the deployed contract but this required a bit of a tweak. So here’s how I did it.

Deploying Via A Delegate

This was done using a simple node script and the process looks like this:

  • Unlock your ‘sender’ account and ‘delegate’ account
  • Compile the smart contract to get the bytecode
    • In this case I used Truffle to compile and accessed the bytecode from the artificat file
  • Create a transaction object with the bytecode as the call data
  • Sender signs the transaction object
  • Delegate sends the signed transaction
  • Note the receipt so you have access to the deployed contract address

I’ve included the code I used for this below.

Using Truffle With The Deployed Contract

Because the contract was deployed using the script the Truffle artificats, etc don’t have the required information so my Dapp couldn’t interact. By making a few manual changes I managed to get it to work:

  • Make sure your contract has been compiled previously using Truffle it should have an artifact file.
  • Find your Truffle artificat file for the contract. It should be of form YouContract.json and is probably under a ‘contract’ folder in your Dapps project.
  • Find the “networks” section of the artificact.
  • Add a new network entry with the following info:
    • Network ID – should be the network that you deployed to.
    • Address – The address your contract was deployed to (from the receipt)
    • TransactionHash – I don’t think this is actually required but it was handy to record it.
    • My entry is shown in the gist below.
  • That’s it! Your Dapp should now work with the deployed contract.

Code Gist

https://gist.github.com/johngrantuk/453de92c28bfae6848ebe13e3e62c74f

Photo by David Travis on Unsplash

NuCypher & Proxy Re-encryption

Photo by Joshua Sortino on Unsplash

 

In April I entered (and won!) the NuCypher+CoinList hackathon. I didn’t actually know much about the NuCypher tech before I got started but once I had built my DApp it was clear this is really interesting stuff and it’s stuck with me ever since as something interesting to build on.

Proxy Re-encryption

The NuCypher solution will eventually provide a decentralised privacy infrastructure but during the hackathon I was mainly making use of a subset of the tech, Proxy Re-encryption.

Proxy re-encryption is a set of encryption algorithms that allow you to transform encrypted data. Specifically… it allows you to re-encrypt data — so you have data that’s encrypted under one set of keys, you can re-encrypt the data without de-encrypting it first, so that now it’s encrypted under a second, different set of keys —NuCypher co-founder MacLane Wilkison

So What?

To understand why this is pretty awesome imagine I have some encrypted data I want to share with Bob, what are the options to do this?

Crazy way – I just give me private encryption key to Bob (who I’m sharing the data with) who can use it to decrypt the data. But now Bob has my key and who knows where this ends up.

Inefficient way – I decrypt the encrypted data then rencrypt it using Bobs public key. This is more secure for sure but I have to do a lot more work. What if I have to do this many times? What if the encrypted data is stored and accessed over a network? Hows the information all being shared? Intensive!

How about the Proxy Re-encryption way:

With Proxy Re-encryption I encrypt the data once.

The encrypted data can be stored anywhere — Amazon, Dropbox, IPFS, etc. I only need to upload it once and provide access to the Proxy service (eventually this will be a NuCypher decentralised service)

The Proxy can rencrypt the data for anyone else I choose (provided I have their public key) efficiently and without ever having access to the decrypted data.

Bob decrypts the data using his own key and resources.

If the data I’m sharing is a stream, i.e. a Twitter feed, then I can enable/revoke decryption access whenever I want — i.e. I can stop someone seeing the data.

NuCypher will eventually provide a decentralised privacy infrastructure which will replace a centralized proxy with a decentralized network. A really good overview of the NuCypher solution is here.

Combine all this with decentralised smart contract as a source of access — very cool!

My DApp — thisfeedisalwaysforsale

My DApp was innspired by Simon de la Rouvieres This Artwork Is Always On Sale where he implements a Harberger Tax on the ownership of a digital artwork. In my app, instead of an artwork, access to a feed of data is always for sale. NuCypher is used to encrypt the data and only the current Patron can decrypt (using NuCypher) to get access. Anyone can buy this access from the current Patron for the sale price set when they took ownership. Whilst they hold ownership they pay a 5% fee to the feed owner. In the demo app the data is a Twitter like feed but the concept could be extended to have more than one Patron and could also be used for other kinds of feed data such as sensor data, camera/video feeds, music, etc.

I was super happy to get a mention in Token Economy as Stefanos favourite entry!

Cool Crypto — Sending Value With A Link

One of the functions I found the most interesting was the ability to send some value, in this case xDai, to someone using a link (try out the app here). The functionality behind this method is pretty cool and makes use of a lot of web3/crypto fundamentals. I found it interesting to dig into it and thought it would be worth sharing.

To Begin

First of all, the Dapp isn’t really ‘sending’ the xDai, it’s more of a deposit/claim pattern with some cool cryptography used to make sure only the person with the correct information, provided by the sender via the link, can claim the value.

Secondly there are two parts — the web Dapp and the smart contract on the blockchain. The web Dapp is really just a nice way of interacting with the smart contract.

Step By Step

A step by step description helped me get it into my head. Please note that I’m not showing all the details of the code here, it’s more a high level description to show the concept.

Sending

Using the Dapp the ‘sender’ enters the amount they want to send from and hits send.

App Send Screen

Once send is hit the Dapp does a bit of work in the background to get the inputs required by the smart contract set-up.

The Dapp uses web3.js to hash some random data:

let randomHash = web3.utils.sha3("" + Math.random());

Now the Dapp uses web3.js to generate a random account which will have a private key and a public key:

let randomWallet = web3.eth.accounts.create();

The random hashed data is then signed using the random wallet private key (see below for more details on signing, etc):

let sig = web3.eth.accounts.sign(randomHash, randomWallet.privateKey);

The Dapp sends a transaction to the blockchain smart contract with the value equal to the amount that is being sent along with the signature and the hashed data:

Contract.send(randomHash, sig.signature), 140000, false, value ...
// This is just a pseudo code to give the gist, see the repo for the full code

The smart contract contains a mapping of Fund structures to bytes32 ID keys:

struct Fund {
    address sender;
    address signer;
    uint256 value;
    uint256 nonce;
    bool claimed;
}

mapping (bytes32 => Fund) public funds;

When the Dapp ‘sends’ the value the smart contract creates a new Fund structure with the signer field set to the public key of the random wallet created by the Dapp. This field is important as it is used as the check when a claim is made:

newFund = Fund({
    sender: msg.sender,
    signer: randomWallet.publicKey,
    value: msg.value,
    nonce: nonce,
    claimed: false
})

Now the newFund is mapped using the randomHash value as the key:

funds[randomHash] = newFund;

The xDai from the sender has now been sent to the smart contract and is ready to be claimed by anyone with the required information.

Finally the Dapp generates a link with the randomHash and random wallet private key as the link parameters:

Link Format: xDai.io/randomHash;privateKey

The link can then be copied and sent via WhatsApp, SMS, etc.

The Claim:

Here it’s probably worth noting the link is really just a nice way to share the important information required to claim the xDai. The Dapp also does the hard work of interacting with the blockchain smart contract.

When the link is visited the Dapp parses the randomHash and the privateKey from the link.

It then signs a message using the privateKey from the link:

let accountHash = web3.utils.sha3(claimAccount);
let sig = web3.eth.accounts.sign(accountHash, privateKey);

Now the smart contract claim function is called using the signature and the original data:

Contact.claim(accountHash, sig, randomHash, claimAccount)

The Solidity ecrecover function is used to get the public address from the signature (this is the magic, see the info below):

address signer = recoverSigner(accountHash, sig);

Finally, the smart contract checks the fund with key matching randomHash has the ‘signer’ equal to the address recovered from the signature. If it does then it can send the value to the claimers account:

if(funds[randomHash].signer == signer && funds[randomHash].claimed == false){    
    funds[randomHash].claimed = true;
    claimAccount.send(funds[randomHash].value);
}

Phew, that’s it! It feels like a lot going on but the basics is it’s a smart way for a user to store value in a smart contract that can only be claimed with the correct information without showing what that information is on the public blockchain.

The Cool Crytography

Signing, ecrecover, eh what?? There’s some things that are probably worth going into a bit more detail.

Wallets, Accounts, etc

An account generated with web3.eth.accounts.create() has it’s own a private key and public key. More info can be found in the docs and here. The private and public keys are linked through an algorithm that has signing and validation properties.

Signing & Validating

The following is a very brief summary of this helpful post.

Signing is the act of a user “signing” data that anyone can validate came from that user.

The signing function will take in a private key and the data. The output will be another string that is the signature.

To validate the signature is from the owner of the private key the signature, the original data and the public key is required.

A validator function is run that recovers the public key from the signed data.

The recovered public key is then compared to the original one and if both are the same the signature is valid.

ecrecover

In this case the Solidity ecrecover (Eliptic Curve Recover) function is used to recover the address associated with the public key. The recoverSigner function in the smart contract code shows an example of how this is done and this is a pretty decent explanation of what is going on.

I think that’s a pretty awesome example of crypto in action!

Ethereum — Scaling Is Not The Problem?

Warp Speed! Photo by chuttersnap on Unsplash

The reality is that State Channels are here now, and work great, today. We can ship commercial quality dapps that use them NOW — JezSan FunFair

The quote above was from a Reddit post highlighted in the November issue of Week In Ethereum. It confirmed something that I didn’t really appreciate until I went to Devcon IV — scaling isn’t the issue that is preventing main stream adoption of Ethereum. It’s the fact that the dApps out there don’t really offer any real improvement over the existing solutions for the main stream user.

As Jez says further down the post — State Channels are a useful and immediate form of scaling and they work today.

Both FunFair and SpankChain already have products that can scale and that in look and feel matches what’s already out there. But that’s the problem—to a normal user they are the same as what’s out there already, they’re not actually any better. In fact by the time you add in all the hassle of dealing with ‘crypto’ it’s actually worse.

It’s Just Not Good Enough

I actually experienced this first hand at the FunFair DevCon party. I took a non-crypto friend along and I explained the concept to him while we watched a demo on a big screen. He appreciated the look and quality of the demo but asked why should he chose this over any other?

Why should we use the dApp? — Photo by Ken Treloar on Unsplash

One of the FunFair developers actually tried to answer – ‘it’s provably fair’. My friend answered, quite fairly, with how can he, himself, verify that it’s fair? (I have to admit I’m fairly ok with code but I would find it hard and he has no experience) The FunFair guy admitted this is a problem.

Next suggestion was that he would always have control over his funds so there’s no issue with an operator delaying or withholding them. To my friend this just wasn’t a problem he had ever experienced so he didn’t really see it as a benefit.

I need what tokens to use it? Ok, lets just leave it there! Basically it came down to the fact that for my friend there was no real improvement and actually the whole thing was a bit of a pain in the ass to use.

It was a an eye opener for me as I’ve always been a big fan of FunFair and I really like the tech — but I guess I’m not a mainstream customer, in fact I’m not actually a customer at all! (I still reckon FunFair will crack it though 😄) When I think about in general the stuff I have used isn’t very user friendly and it’s normally just my enthusiasm for the tech that pushes me to use it.

BUIDL To The Rescue

So what to do? Well at Devcon there was a lot of focus on design and user experience so obviously smarter people realised this issue a good while before me! And smart people are experimenting and building. Universal Logins are super cool. Week In Ethereum also linked to some details about the work of Austin Griffith at Gitcoin labs. I think this is the kind of thing that’s going to drives things forward and there’s a lot of room to get involved.

Check out some of the stuff below, dive in and experiment.