To get started with Solidity + Truffle

See our tutorial on Truffle and Solidity

To get started with Golang Contracts

See our Golang Contracts

To get started with universal transaction signing

Basechain has the ability to verify and accept transactions signed by native Ethereum wallets. In a nutshell, users can simply use their MetaMask account or any other native Ethereum wallet — further blending the line between Layer 1 and Layer 2.

In the next sections, we'll briefly walk you through the setup required to enable universal transaction signing in your app.

Connecting to Extdev Testnet

The first thing we want to do is to instantiate a new Client:

  _createClient () {
    const chainId = this.extdevConfig['chainId']
    const writeUrl = this.extdevConfig['writeUrl']
    const readUrl = this.extdevConfig['readUrl']
    const client = new Client(chainId, writeUrl, readUrl)
    return client
  }

Pretending to be Metamask

Once the client gets instantiated, we must force personal sign by pretending to be MetaMask no matter what the web3 provider actually is:

    const ethProvider = web3js.currentProvider
    ethProvider.isMetaMask = true

Setting Up a Signer

Next, let's setup a a signer and then get the callerAddress (that is your address on Ethereum or Rinkeby):

  async _setupSigner (loomClient, provider) {
    const signer = getMetamaskSigner(provider)
    const ethAddress = await signer.getAddress()
    const callerAddress = new Address('eth', LocalAddress.fromHexString(ethAddress))

    loomClient.txMiddleware = [
      new NonceTxMiddleware(callerAddress, loomClient),
      new SignedEthTxMiddleware(signer)
    ]

    return callerAddress
  }

This allows us to create the default set of tx middleware required to successfully commit transactions to Loom.

Creating a Temporary Loom Provider

Now, we can create a temporary provider for Loom:

  async _createLoomProvider (client, callerAddress) {
    const dummyKey = CryptoUtils.generatePrivateKey()
    const publicKey = CryptoUtils.publicKeyFromPrivateKey(dummyKey)
    const dummyAccount = LocalAddress.fromPublicKey(publicKey).toString()
    const loomProvider = new LoomProvider(
      client,
      dummyKey,
      () => client.txMiddleware
    )
    loomProvider.setMiddlewaresForAddress(callerAddress.local.toString(), client.txMiddleware)
    loomProvider.callerChainId = callerAddress.chainId
    // remove dummy account
    loomProvider.accounts.delete(dummyAccount)
    loomProvider._accountMiddlewares.delete(dummyAccount)
    return loomProvider
  }

Note that we're using a dummy key and, once LoomProvider and instantiated and the middlewares are added, it is no longer needed.

Checking Mappings

At this point, we want to check if your Ethereum account has been already mapped:

    let accountMapping = await this._loadMapping(callerAddress, client)
    if (accountMapping === null) {
      console.log('Create a new mapping')
      const signer = getMetamaskSigner(ethProvider)
      await this._createNewMapping(signer)
      accountMapping = await this._loadMapping(callerAddress, client)
      console.log(accountMapping)
    } else {
      console.log('mapping already exists')
    }

Here's how the function that check if a mapping exists looks like:

  async _loadMapping (ethereumAccount, client) {
    const mapper = await AddressMapper.createAsync(client, ethereumAccount)
    let accountMapping = { ethereum: null, loom: null }
    try {
      const mapping = await mapper.getMappingAsync(ethereumAccount)
      accountMapping = {
        ethereum: mapping.from,
        loom: mapping.to
      }
    } catch (error) {
      console.error(error)
      accountMapping = null
    } finally {
      mapper.removeAllListeners()
    }
    return accountMapping
  }

Adding a New Mapping

If a mapping doesn't exist we'll add it with:

  async _createNewMapping (signer) {
    const ethereumAccount = await signer.getAddress()
    const ethereumAddress = Address.fromString(`eth:${ethereumAccount}`)
    const loomEthSigner = new EthersSigner(signer)
    const privateKey = CryptoUtils.generatePrivateKey()
    const publicKey = CryptoUtils.publicKeyFromPrivateKey(privateKey)
    const client = this._createClient()
    client.txMiddleware = createDefaultTxMiddleware(client, privateKey)
    const loomAddress = new Address(client.chainId, LocalAddress.fromPublicKey(publicKey))

    const mapper = await AddressMapper.createAsync(client, loomAddress)
    try {
      await mapper.addIdentityMappingAsync(
        ethereumAddress,
        loomAddress,
        loomEthSigner
      )
      client.disconnect()
    } catch (e) {
      if (e.message.includes('identity mapping already exists')) {
      } else {
        console.error(e)
      }
      client.disconnect()
      return false
    }
  }

Again, note that once the mapping is created, the private key is not needed anymore.

Instantianting the contract

For the scope of this example, we are using the SimpleStore smart contract. Instantiating it is as simple as:

  async _getContract () {
    this.contract = new this.web3loom.eth.Contract(SimpleStoreJSON.abi, SimpleStoreJSON.networks[this.extdevConfig['networkId']].address)
  }

Universal transaction signing in action

Now, to execute the set method of our smart contract, we can do something like this:

  async _setValue () {
    const ethAddress = this.accountMapping.ethereum.local.toString()
    const value = parseInt(this.counter, 10)
    await this.contract.methods
      .set(value)
      .send({
        from: ethAddress
      })
  }

If everything works well you should see a Metamask popup asking you to confirm the transaction:

Eth Signing

Note that you just signed transactions on Loom without even accessing your eth wallet keys😉.

Demo Project

We've built a small demo project to showcase this functionality. The source code is available here.

Other Wallets

If you use Portis or Fortmatic, head over to this page.