_images/logo.png

This documentation is DEPRECATED.

Some descriptions might be invalid.

Universal Login documentation

Universal Login is a design pattern for storing funds and connecting to Ethereum applications, aiming to simplify new users on-boarding.

This documentation covers all components of the project including sdk, relayer and smart contracts.

Disclaimer

Universal Login is a work in progress and is at the experimental stage. Expect breaking changes. The code is not stable and has not been audited and therefore should not be used in a production environment

Getting started

This documentation is DEPRECATED.

Some descriptions might be invalid.

Universal Login helps you build user-friendly Ethereum based applications.

To quickly start building an application go to one of tutorial sections below:

To learn Universal Login concepts and architecture go to one of overview sections:

To get API reference go to one of the following sections:

Overview

This documentation is DEPRECATED.

Some descriptions might be invalid.

Introduction
Technical concepts

Technically Universal Login utilizes four major concepts:

  • Personal multi-sig wallet - a smart contract used to store personal funds. A user gets his wallet created in a barely noticeable manner. The user then gets engaged incrementally to add authorization factors and recovery options.
  • Meta-transactions - that gives user ability to interact with the smart contract from multiple devices easily, without a need to store ether on each of those devices. Meta-transactions enable payments for execution with tokens.
  • ENS names - naming your wallet with easy-to-remember human-readable name
  • Universal login - a wallet name can be used to log in to dapps, web, and native applications
Components

Universal Login has four components. All components are stored in one monorepo available here. Components are listed below:

  • Contracts - smart contracts used by Universal Login, along with some helper functions
  • Relayer - HTTP REST server that relays meta-transactions to Universal Login smart contracts
  • SDK - javascript API, a thin communication layer that interacts with the Universal Login ecosystem, via both relayer and Ethereum node.
  • React - typescript library, that contains Universal Login main components to use in react applications.
Dependencies

The diagram below shows dependencies between components.

_images/Packages.png

The external interfaces present in the Universal Login system are identified by the lollipop use symbol:

<<IF-6>> RELAYER HTTP JSON IF
this interface defines an off-chain remote API for ERC #1077 and #1078
<<IF-9>> ETH JSON-RPC IF
this interface is the Ethereum JSON-RPC API for the on-chain execution

The internal interfaces defined within the Universal Login system are identified by the arrow use symbol. The main ones are:

<<IF-2>> UL SDK IF
the JS applications using Universal Login shall be based on this library interface to conveniently attach to the Relayer subsystem and route their meta transactions
<<IF-4>> ERC1077 SIG IF
this interface is a message hash and signature JS facility API for ERC #1077
<<IF-5>> ERC1077 IF / ERC1078 IF
this interface is made up of ERC #1077 and #1078 smart contracts ABI
Main concepts
Deployment

Deployment is designed in a way that makes the user pay for himself. To do that, we use counterfactual deployment (create2 function to deploy contract).

The process looks as follows:

  • computing contract address - The SDK computes a deterministic contract address. A contract address is unique and is obtained from the user’s public key and a factory contract address connected to a particular relayer.
  • waiting for balance - The user sends funds to this address. He can transfer ether on his own or use an on-ramp provider. The SDK waits for the future contract address balance to change. If the SDK discovers that required funds appear at this address, it sends a deploy request to the relayer.
  • deploy - The relayer deploys the contract and gets a refund from it immediately.
  • refund - During deployment the contract will refund the cost of the transaction to the relayer address.
Deployment in-depth

An SDK creates deployment. The deployment contains the following parameters:

_images/Deployment.png
  • ensName - ENS name chosen by a user. It is the only parameter provided by the user.
  • publicKey - the public key of a newly generated key pair on the user’s device.
  • ensAddress - the address of ENS contract. It is required to properly register the ENS name.
  • resolverAddress - the address of Resolver contract. It is required to properly register the ENS name.
  • registrarAddress - the address of Registrar contract. It is required to properly register the ENS name.
  • gasPrice - gas price used in the refund process.
  • signature - the signature of all of the arguments above. The signature ensures parameters come from the owner of the private key (paired to the public key). In particular, it prevents against malicious ENS name registration and gas price replacement.

ENS name collision

Deployment contains ENS name so it could fail (for example when ENS name is taken). That’s why we require success on register ENS name. If it fails, the contract won’t be deployed, so the user can choose ENS name once again and register it on the same contract address.

Deployment lifecycle

It starts when the user generates a contract address assigned to him. The first half of deployment is waiting for the user to send funds to the computed contract address.

_images/DeploymentStates.png
Meta-transactions

Message (also known as meta-transaction or signed messages) is a way to trigger ethereum transaction from an application or device that does not possess any ether. The message states the intention of the user. It requests a wallet contract to execute a transaction. (eg: funds transfer, an external function call or an internal function call - i.e. an operation in the wallet contract itself). An application sends a message signed with one or more of the keys whitelisted in the contract to the relayer server. The relayer than wraps the message into an ethereum transaction. The message is then processed by the contract as a function call. The relayer wallet is paying for transaction gas. The wallet contract refunds the cost of execution back to the relayer in ether or ERC20 token. The message contains the following parameters:

_images/Message.png
  • to - the recipient of the message call
  • from - the address of contract that executes the message
  • value - number of Wei to send
  • data - data for the transaction (i.e. an encoded function call)
  • gasToken - address of token used for refund
  • gasLimit - maximum gas to use in for a specific transaction
  • gasPrice - gas price to use in the refund process
  • nonce - an internal nonce of the transaction relative to the contract wallet
  • operationType - the type of execution (call, delegatecall, create)
  • signature - the signature of all of the arguments above, which ensures parameters come from the owner of the allowed public-private key pair

Message lifecycle

A message starts its journey when it is created and signed by a user (i.e. an application or an SDK) and then sent to a relayer. In the relayer it goes through the following states:

_images/MessageStates.png
  • await signature optional- The relayer waits to collect all the required signatures if the message requires more than one.
  • queued - The message is queued to be sent.
  • pending - The message is propagated to the network and waits to be mined. In a pending state, the message has a transaction hash.
  • sucess / error - A mined transaction is a success or an error. In a success state, the content of the message status is not changed. In an error state, the message has an error message.
New device connection

One of the key activities is connecting a newly created public key to the existing smart contract wallet. The new public key is created on a new device or application that never interacted with the smart contract wallet before. See below.

_images/setup.png

The new public key is added using a meta-transaction. The meta-transaction needs to be signed with the private key from a device that is already authorized in the wallet smart contract. After signing, the meta-transaction is sent to the relayer, which propagates it to the blockchain. The picture below shows this process.

_images/expected.png

There are four key actors in the process:

  • Old device or an application that is already authorized. Authorized means that there exists a public-private key pair, where the private key is kept on the device and the public key is stored in the wallet smart contract on the blockchain.
  • New device (or a new application) that we want to authorize to use the wallet smart contract. To do that we need to generate a new public-private key pair and add the new public key to the wallet contract as a management or action key. The public key is added by creating a meta-transaction signed by the old device (old private key) and sending it to the relayer.
  • Relayer - relays meta-transaction sent from an old device to the blockchain
  • Smart Contract Wallet - a smart contract that stores keys and executes meta-transactions.

Possible attacks

The problem might seem pretty straightforward, but there are some complexities to consider. In particular, we should avoid introducing the possibility of the following attacks:

  • Man in the middle

A man-in-the-middle attack can happen when the new device sends the new public key to the old device. A malicious actor that intercepts communication (e.g. a relayer) can replace the new public key with a public key that belongs to him and, as a result, take over control of the wallet contract.

_images/man-in-the-middle.png
  • Spamming

A spam attack can happen when a lot of new devices request to connect to an old device, therefore the old device is spammed with many notifications.

_images/spamming.png

Solution 1

The first solution is pretty straightforward. A new device transfers its public key to the old device.

_images/solution-1.png

Transfer means

There are two possible ways of transferring the public key.

Note: This is a public key, so we don’t worry about intercepting.

Note: The seed for the ecliptic curve key that we use has 128 bits or 16 bytes.

  • Scan the QR code

  • Manually copy the public key by typing. That might have different shades.

    • Retype the letters (32 chars if hex or 26 with just mix cased letters + digits).
    • Use emojis (12 emojis with 1000 emoji base), see the example interface below.
    _images/emoji.png
    • If both applications are on the some one device -> copy paste. (or in some cases even send by e-mail)

Solution 2

The second solution might be useful if, for some reason, we want to transfer information from an old device to a new device. That might make a difference in the case of using QR codes and the old device does not possess a camera.

The process goes as follows:

  1. The old device generates a temporary key pair.
  2. The temporary private key gets transferred to the new device.
  3. The new device encrypts a new public key using the temporary private key and transfers it to the old device.
  4. The old device decrypts the new device’s public key and sends a meta-transaction to the relayer adding it to the wallet smart contract.
_images/solution-2.png

Solution 3

The third solution is an alternative to the previous solutions. The new device generates a new key pair and shows the user emojis based on a hash of the new public key to the later use on an old device. The newly generated public key is sent to a relayer and forwarded to the old device. To finalize connection of a new device, the user has to arrange emojis on the old device in the same order he has seen on the new device. See below.

_images/solution-3.png

In case of spamming attack in place, the user has to type the emojis manually.

Development environment

Development environment helps quickly develop and test applications using Universal Login. The script that starts development environment can be run from @unilogin/ops project. The script does a bunch of helpful things:

  • creates a mock blockchain (ganache)
  • deploys a mock ENS
  • registers three testing ENS domains: mylogin.eth, universal-id.eth, popularapp.eth
  • deploys an example ERC20 Token that can be used to pay for transactions
  • creates a database for a relayer
  • starts a local relayer

Read more in tutorial

Tutorial

This documentation is DEPRECATED.

Some descriptions might be invalid.

Quickstart
Starting with Web3 provider

If you are developing a web application the easiest way to get started with Universal-Login is by using our Web3 provider.

Installation

To add the provider to your project using npm type the following:

npm i web3 @unilogin/web3 react react-dom

If you are using yarn than type:

yarn add @unilogin/web3 react react-dom
Creating the provider
const ulProvider = ULWeb3Provider.getDefaultProvider('mainnet');
const web3 = new Web3(ulProvider);

Now you can continue writing your application as a normal Web3 DApp. To read more how to write web3 app go to Web3 docs.

User onboarding

First time you send a transaction the modal will appear asking users to create an account. After user finishes the onboarding flow the transaction will be executed as normal

You can also trigger an early onboarding (for example if you have a “login” button on your website). To do so call the create on the provider:

await ulProvider.create();

The promise will be resolved once the user completes the flow.

Detecting Universal-Login

To detect whether the web3 is running with UniversalLogin use the following code:

const usingUniversalLogin = web3.currentProvider && web3.currentProvider.isUniLogin;
Starting with SDK
Installation

To add the SDK to your project using npm type the following:

npm i @unilogin/sdk

If you are using yarn than type:

yarn add @unilogin/sdk
Development environment
Prerequisites
Before running the development environment, make sure you have PostgreSQL installed, up and running.
Installation

To use the development environment, you need to install @unilogin/ops as dev dependency to your project.

With npm:

npm install @unilogin/ops --save-dev

With yarn:

yarn add --dev @unilogin/ops -D
Adding a script

The simplest way to use the development environment is to add a script to package.json file:

...
"scripts": {
  ...
  "start:dev": "universal-login start:dev"
}
Running development environment

To start the development environment type in your console:

yarn start:dev

Which will start the development environment. The output should look somewhat like this:

Wallets:
  0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff - 0x29f3edee0ad3abf8e2699402e0e28cd6492c9be7eaab00d732a791c33552f797
  0x63FC2aD3d021a4D7e64323529a55a9442C444dA0 - 0x5c8b9227cd5065c7e3f6b73826b8b42e198c4497f6688e3085d5ab3a6d520e74
  0xD1D84F0e28D6fedF03c73151f98dF95139700aa7 - 0x50c8b3fc81e908501c8cd0a60911633acaca1a567d1be8e769c5ae7007b34b23
  0xd59ca627Af68D29C547B91066297a7c469a7bF72 - 0x706618637b8ca922f6290ce1ecd4c31247e9ab75cf0530a0ac95c0332173d7c5
  0xc2FCc7Bcf743153C58Efd44E6E723E9819E9A10A - 0xe217d63f0be63e8d127815c7f26531e649204ab9486b134ec1a0ae9b0fee6bcf
  0x2ad611e02E4F7063F515C8f190E5728719937205 - 0x8101cca52cd2a6d8def002ffa2c606f05e109716522ca2440b2cc84e4d49700b
  0x5e8b3a7e6241CeE1f375924985F9c08706f41d34 - 0x837fd366bc7402b65311de9940de0d6c0ba3125629b8509aebbfb057ebeaaa25
  0xFC6F167a5AB77Fe53C4308a44d6893e8F2E54131 - 0xba35c32f7cbda6a6cedeea5f73ff928d1e41557eddfd457123f6426a43adb1e4
  0xDe41151d0762CB537921c99208c916f1cC7dA04D - 0x71f7818582e55456cb575eea3d0ce408dcf4cbbc3d845e86a7936d2f48f74035
  0x121199e18C70ac458958E8eB0BC97c0Ba0A36979 - 0x03c909455dcef4e1e981a21ffb14c1c51214906ce19e8e7541921b758221b5ae

Node url (ganache): http://localhost:18545...
      ENS address: 0x67AC97e1088C332cBc7a7a9bAd8a4f7196D5a1Ce
Registered domains: mylogin.eth, universal-id.eth, popularapp.eth
    Token address: 0x0E2365e86A50377c567E1a62CA473656f0029F1e
      Relayer url: http://localhost:3311
Using the SDK
Creating a wallet contract

To start using the SDK you will need to create an SDK instance and deploy a wallet contract. Below is a snippet doing precisely that for the development environment.

import UniversalLoginSDK from '@unilogin/sdk';

const universalLoginSDK = new UniversalLoginSDK('http://localhost:3311', 'http://localhost:18545');
const [privateKey, contractAddress] = await sdk.create('myname.mylogin.eth');

The first argument of UniversalLoginSDK constructor is a relayer address, second is an Ethereum node address.

Sending a meta-transaction

Once you have the contract wallet deployed you can execute a transaction via relayer:

const message = {
  from: '0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA',
  to: '0xbA03ea3517ddcD75e38a65EDEB4dD4ae17D52A1A',
  data: '0x0',
  value: '500000000000000000',
  gasToken: '0x9f2990f93694B496F5EAc5822a45f9c642aaDB73',
  gasPrice: 1000000000,
  gasLimit: 1000000
};

await sdk.execute(message, privateKey);

Note: from field in this case is the contract address.

Most fields of the message are analogous to a normal Ethereum transaction, except for gasToken, which allows to specify the token in which transaction cost will be refunded.

The token need to be supported by a relayer. The wallet contact needs to have enough token balance to refund the transaction.

A detailed explanation of each method can be found in subsections of the SDK documentation: creating SDK, creating wallet contract and execute.

Connecting to an existing app on testnet
Create a wallet contract

Create your own wallet contract using Universal Login Wallet and get your contract address.

Create UniversalLoginSDK

In your project, create the UniversalLoginSDK

import UniversalLoginSDK from '@unilogin/sdk';
import ethers from 'ethers';


const relayerUrl = 'https://relayer-mainnet.universallogin.io';
const jsonRpcUrl = 'https://mainnet.infura.io';

const universalLoginSDK = new UniversalLoginSDK(relayerUrl, jsonRpcUrl);
Start listening for events

Then make UniversalLoginSDK start listening for relayer and blockchain events

sdk.start();
Request a connection

Now, you can request a connection to the created wallet contract

const privateKey = await sdk.connect('YOUR_CONTRACT_ADDRESS');
Subscribe to KeyAdded

Subscribe to KeyAdded event with your new key filter

const key = new ethers.Wallet(privateKey).address;
const filter =
  {
    contractAddress: 'YOUR_CONTRACT_ADDRESS',
    key
  };

const subscription = sdk.subscribe(
  'KeyAdded',
  filter,
  (keyInfo) =>
    {
      console.log(`${keyInfo.key} now has permission to manage wallet contract`);
    });
Accept a connection request

Accept a connection request in Universal Login Example App. After that your newly created key has a permission to manage your wallet contract.

Stop listening for events

Remember to stop listening for relayer and blockchain events

sdk.stop();
Helpers
Prerequisites

Install the universal-login toolkit:

yarn global add @unilogin/ops
Test token

To deploy a test token use the deploy:token script universal-login deploy:token --nodeUrl [url] --privateKey [privateKey]

Example:

universal-login deploy:token --nodeUrl http://localhost:18545 --privateKey 0x29f3edee0ad3abf8e2699402e0e28cd6492c9be7eaab00d732a791c33552f797
Sending funds

To send funds to an address use the send script universal-login send [to] [amount] [currency] --nodeUrl [url] --privateKey [privateKey]

Parameters:
  • to - the address to send funds to
  • amount - the amount to send
  • currency - the currency of transfer
  • nodeUrl (optional) - JSON-RPC URL of an Ethereum node, set to http://localhost:18545 by default
  • privateKey (optional) - the private key of a wallet with additional balance, set to DEV_DEFAULT_PRIVATE_KEY by default which corresponds to a wallet that has enough ethers

Example:

universal-login send 0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA 4 ETH
ENS registration

To use Universal Login with your own ENS domain, you will need to register it, connect to the resolver and deploy your own registrar. There is a script for that.

Note: the script currently works only for .test domains. Tested on the Rinkeby and the Ropsten test networks.

You can register the domain in two ways: from command line and programmatically. To use a registered domain in your relayer, type its name in relayer config.

From command line

To register an .eth ENS domain type in the console:

universal-login register:eth:domain [my-domain] --ensAddress [ensAddress] --privateKey [privateKey] --nodeUrl [url] --gasPrice [gasPrice]
Parameters:
  • my-domain - a domain to register
  • ensAddress : string - the address of an ENS contract ([list of ENS addresses](https://docs.ens.domains/ens-deployments) on public networks)
  • privateKey : string - private key to execute registrations. Note: You need to have ether on it to pay for contracts deployment.
  • nodeUrl : string - JSON-RPC URL of an Ethereum node
  • gasPrice : string optional - gas price of transactions, default gas price value is 9 gwei

To register cool-domain.eth (each transaction’s gasPrice set to 11 gwei):

universal-login register:eth:domain cool-domain --ensAddress 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e --privateKey 'YOUR_PRIVATE_KEY' --nodeUrl 'https://mainnet.infura.io' --gasPrice 11000000000

Result:

Registering cool-domain.eth...
New public resolver deployed: 0x75242e98198486fe0307e5ee307f340Af5c950a4
Resolver for cool-domain.eth set to 0x75242e98198486fe0307e5ee307f340Af5c950a4 (public resolver)
New registrar deployed: 0x09873b81932b7726595b86Fe8612c82e65bdB9Fe
cool-domain.eth owner set to: 0x09873b81932b7726595b86Fe8612c82e65bdB9Fe (registrar)

Note: You must be the owner of the domain that means you need to buy it. You can buy an ENS domain for example `here<https://ethsimple.com/>`_

To register an .test ENS domain type in the console:

universal-login register:test:domain [my-domain] [publicResolverAddress] --ensAddress [ensAddress] --privateKey [privateKey] --nodeUrl [url]
Parameters:
  • my-domain - a domain to register
  • publicResolverAddress : string - the address of a public resolver. For the Mainnet a working public resolver address is 0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41.
  • ensAddress : string - the address of an ENS contract (`list of ENS addresses<https://docs.ens.domains/ens-deployments>`_ on public networks)
  • privateKey : string - private key to execute registrations. Note: You need to have ether on it to pay for contracts deployment.
  • nodeUrl : string - JSON-RPC URL of an Ethereum node

To register cool-domain.test on a test network that supports registration of test domains (e.g. Ropsten, Rinkeby, Görli) and connect it to resolver at address 0x4C641FB9BAd9b60EF180c31F56051cE826d21A9A type following:

universal-login register:test:domain cool-domain 0x4C641FB9BAd9b60EF180c31F56051cE826d21A9A --ensAddress 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e --privateKey 'YOUR_PRIVATE_KEY' --nodeUrl 'https://ropsten.infura.io'

Result:

Registering cool-domain.test...
Registrar address for test: 0x21397c1A1F4aCD9132fE36Df011610564b87E24b
Registered cool-domain.test with owner: 0xf4C1A210B6436eEe17fDEe880206E9d3Ab178c18
Resolver for cool-domain.test set to 0x4C641FB9BAd9b60EF180c31F56051cE826d21A9A (public resolver)
New registrar deployed: 0xf1Af1CCEEC4464212Fc7b790c205ca3b8E74ba67
cool-domain.test owner set to: 0xf1Af1CCEEC4464212Fc7b790c205ca3b8E74ba67 (registrar)

Note: use .test tld only on testnets.

Programmatically

To register your own ENS domain programmatically, you should use DomainRegistrar.

new DomainRegistrar(config)

creates DomainRegistrar.

Parameters:
  • ensInfo : object - required informations about ENS:
    • ensAddress : string - the address of an ENS contract
    • publicResolverAddress : string - the address of a public resolver
  • wallet : ethers.Wallet - instance of ethers Wallet connected to the specific network
Returns:
DomainRegistrar instance
Example:
import {providers, Wallet} from 'ethers';

const ensInfo = {
  ensAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
  publicResolverAddress: '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41'
};
const provider = new providers.JsonRpcProvider('https://mainnet.infura.io');
const wallet = new Wallet('YOUR_PRIVATE_KEY', provider);

const registrar = new DomainRegistrar(ensInfo, wallet);
registrar.registerAndSave(domain, tld)

registers a new domain and saves all information about newly registered domain to a new file (a registrar address or resolver address)

Parameters:
  • domain : string - a domain to register
  • tld : string - a top level domain, for example: eth or on testnets: test
Example:
registrar.registerAndSave('new-domain', 'test');
Result:

file named extra-domain.test_info that includes:

DOMAIN='extra-domain.test'
PUBLIC_RESOLVER_ADDRESS='0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41'
REGISTRAR_ADDRESS='0xEe0b357352C7Ba455EFD0E20d192bC44F1Bf8d22'

SDK

This documentation is DEPRECATED.

Some descriptions might be invalid.

An SDK is a JS library that helps to communicate with a relayer. The SDK makes it easy to manage a contract by creating basic contract-calling messages. It uses a private key to sign these messages and send them to the relayer, which propagates them to the network.

Creating an SDK

new UniversalLoginSDK(relayerURL, providerURL, sdkConfig)

Parameters:
  • relayerURL : string - a URL address of a relayer
  • providerURL : string - JSON-RPC URL of an Ethereum node
  • sdkConfig (optional) : object - specific sdk options listed below (each of them is optional):
    • applicationInfo : object - detailed info about application: applicationName, type (e.g. laptop, mobile), logo (image source)
    • paymentOptions : object - specific transaction options: gasLimit, gasPrice, gasToken
    • observedTokensAddresses : array of strings - addresses of tokens used
    • observedCurrencies: array with combination of USD, ETH and DAI - shortcuts of currencies used
    • notice : string - additional info shown on the top of React components e.g. ‘Beta version’
    • executionFactoryTick : number - perdiod of time in miliseconds which factory wait with retry if transaction failed
    • authorizationsObserverTick : number - perdiod of time in miliseconds which last between authorization check
    • balanceObserverTick : number - perdiod of time in miliseconds which last between balance update
    • priceObserverTick : number - perdiod of time in miliseconds which last between price update
Returns:
UniversalLoginSDK instance
Example:
import UniversalLoginSDK from '@unilogin/sdk';

const sdkConfig = {
  applicationInfo: {
    applicationName: 'Universal Login Application',
    type: 'laptop',
    logo: 'http://logo.universallogin.io'
  },
  paymentOptions: {
    gasPrice: 1500000000,
    gasLimit: 2000000,
    gasToken: '0x0000000000000000000000000000000000000000
  },
  observedTokensAddresses: ['0x0000000000000000000000000000000000000000', '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2'],
  observedCurrencies: ['USD', 'ETH'],
  notice: 'This is beta version',
  executionFactoryTick: 1000,
  authorizationsObserverTick: 1000,
  balanceObserverTick: 1000,
  priceObserverTick: 1000
};

const universalLoginSDK = new UniversalLoginSDK(
  'http://myrelayer.ethworks.io',
  'http://localhost:18545',
  sdkConfig
);
Default sdkConfig:
sdkConfig = {
applicationInfo: {
    applicationName: 'Unknown application',
    logo: 'none',
    type: 'unknown'
  },
  paymentOptions: {
    gasPrice: 10000000000,
    gasLimit: 200000,
    gasToken: '0x0000000000000000000000000000000000000000
  },
  observedTokensAddresses: ['0x0000000000000000000000000000000000000000'],
  observedCurrencies: ['USD', 'DAI', 'ETH'],
  notice: '',
  executionFactoryTick: 1000,
  authorizationsObserverTick: 3000,
  balanceObserverTick: 3000,
  priceObserverTick: 300000
};

getWalletContractAddress(ensName)

gets a wallet contract address by an ENS name

Parameters:
  • ensName : string - an ENS name
Returns:
promise that resolves to address if the ENS name is registered or null if the ENS name is available
Example:
const contractAddress = await sdk.getWalletContractAddress('justyna.my-super-domain.test');

walletContractExist(ensName)

checks if an ENS name is registered.

Parameters:
  • ensName : string - an ENS name
Returns:
promise that resolves to true if the ENS name is registered or false if the ENS name is available
Example:
const walletContractExist = await sdk.walletContractExist('justyna.my-super-domain.test');
Creating a wallet contract
createFutureWallet

sdk.createFutureWallet()

Creates a FutureWallet, which contains all information required to deploy and use a Wallet in the future.

Returns:
promise that resolves to FutureWallet.

FutureWallet contains:

  • privateKey - that will be connected to ContractWallet. The key will be used to sign transactions once the wallet is deployed.

  • contract address - an address under which the wallet will be deployed in the future.

  • waitForBalance - a function that waits for a contract address balance change in a way that will allow the wallet contract to be deployed.

    Returns:

    promise, that resolves (only when the wallet contract balance is changed to satisfy relayer requirements) to {tokenAddress, contractAddress}

  • deploy - a function that requests wallet contract deployment.

    Parameters:
    • ensName : string - a chosen ENS name
    • gasPrice : string - gas price of a deployment transaction
    Returns:

    promise that resolves to the deployed wallet contract address

Example:
const {privateKey, contractAddress, waitForBalance, deploy} = await sdk.createFutureWallet();
await waitForBalance();
await deploy('myname.example-domain.eth');
connect

sdk.connect(contractAddress)

requests adding a new key to a contract.

Parameters:
  • contractAddress : string - an address of the contract to manage a connection
Returns:

promise that resolves to privateKey, where:

  • privateKey - the private key that is requested to add to manage the contract
Example:
const privateKey = sdk.connect('0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA');
denyRequest

sdk.denyRequest(contractAddress, publicKey, privateKey)

removes the request for adding a new key from pending authorizations.

Parameters:
  • contractAddress : string - an address of a contract to remove a request
  • publicKey : string - an address to remove from add requests
  • privateKey : string - a private key to sign a request
Returns:

promise that resolves to publicKey, where:

  • publicKey - an address removed from pending authorisations
Example:
const publicKey = await sdk.denyRequest('0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA', '0xb19Ec9bdC6733Bf0c825FCB6E6Da95518DB80D13');
Creating a deployed wallet

new DeployedWallet(contractAddress, name, privateKey, sdk)

creates the DeployedWallet object

Parameters:
  • contractAddress : string - an address of a contract to remove a request
  • name : string - a name for deployed wallet
  • privateKey : string - a private key to sign a request
  • sdk : object - a UniversalLoginSDK object
Returns:

DeployedWallet instance

Example:

import {DeployedWallet} from '@unilogin/sdk';

const deployedWallet = new DeployedWallet('0x2828282882215356332', 'name.mylogin.eth', '0x1183823828282356343143', sdk);
Transaction execution
execute

deployedWallet.execute(message)

executes any message.

Parameters:
  • message : object - a message that is sent to a contract, includes:
    • from : string - an address of the contract that requests execution
    • to : string - a beneficient of this execution
    • data : string - the data of execution
    • value : string - value of transaction
    • gasToken : string - token address to refund
    • gasPrice : number - price of gas to refund
    • gasLimit : number - limit of gas to refund
Returns:

promise that resolves to the Execution

Example:

const message = {...transferMessage, from: contractAddress, gasToken: ETHER_NATIVE_TOKEN.address, data: '0x'};
const {waitToBeSuccess} = await deployedWallet.execute(message);

Execution contains:

  • messageStatus - a current status of the sent message (learn more)
  • waitToBeMined - a function that returns a promise that resolves to MessageStatus once the transaction enclosed with Message is mined
Example:
const message = {
  from: '0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA',
  to: '0xbA03ea3517ddcD75e38a65EDEB4dD4ae17D52A1A',
  data: '0x0',
  value: '500000000000000000',
  gasToken: '0x9f2990f93694B496F5EAc5822a45f9c642aaDB73',
  gasPrice: 1000000000,
  gasLimit: 1000000
};

await deployedWallet.execute(
  message,
);

In this case contract 0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA sends 0.5 eth to 0xbA03ea3517ddcD75e38a65EDEB4dD4ae17D52A1A.

messageStatus
_images/messageStatus.png
  • required : number - the amount of required signatures to execute the message
  • collectedSignatures : string[] - signatures collected by a relayer
  • totalCollected : number - the amount of collected signatures
  • messageHash : string - hash of the message
  • state : MessageState - one of the message states: AwaitSignatures, Queued, Pending, Error, Success
  • transactionHash (optional) : string - a transaction hash is only possible when the message state is Pending, Success or Error
  • error (optional) : string - only when the message state is Error

sdk.getMessageStatus(messageHash)

requests a message status of a specific message

Parameters:
  • messageHash - a hash of a message
Returns:
promise that resolves to MessageStatus
Managing a wallet contract
addKey

deployedWallet.addKey(publicKey, executionOptions)

adds a key to manage a wallet contract.

Parameters:
  • publicKey : string - a public key to manage the contract
  • executionOptions : object - an optional parameter that includes details of transactions for example gasLimit or gasPrice
Returns:
promise that resolves to the Execution
Example:
const executionOptions = {
  gasToken: '0x850437540FE07d02045f88cAe122Bc66B1BdE957',
  gasPrice: 1000000,
  gasLimit: 150000
};
await deployedWallet.addKey(
  '0x96E8B90685AFD981453803f1aE2f05f8Ebc3cfD0',
  executionOptions,
);
addKeys

deployedWallet.addKey(publicKeys, executionOptions)

adds multiple keys to manage a contract.

Parameters:
  • publicKeys : array of strings - public keys to add
  • executionOptions : object - an optional parameter that includes details of transactions for example gasLimit or gasPrice
Returns:
promise that resolves to the Execution
Example:
const publicKeys = [
  '0x96E8B90685AFD981453803f1aE2f05f8Ebc3cfD0',
  '0xb19Ec9bdC6733Bf0c825FCB6E6Da95518DB80D13'
];
const executionOptions = {
  gasToken: '0x850437540FE07d02045f88cAe122Bc66B1BdE957',
  gasPrice: 1000000,
  gasLimit: 150000
};
await deployedWallet.addKeys(
  publicKeys,
  executionOptions,
);
removeKey

deployedWallet.removeKey(publicKey, executionOptions)

removes a key from a contract.

Parameters:
  • publicKey : string - a public key to remove
  • executionOptions : object - an optional parameter that includes details of transactions for example gasLimit or gasPrice
Returns:
promise that resolves to the Execution
Example
const executionOptions = {
  gasToken: '0x9f2990f93694B496F5EAc5822a45f9c642aaDB73',
  gasPrice: 1000000,
  gasLimit: 150000
};
await deployedWallet.removeKey(
  '0xbA03ea3517ddcD75e38a65EDEB4dD4ae17D52A1A',
  executionOptions
);
Events
Key added and key removed

sdk.start()

Starts listening to blockchain events and fetches supported tokens detials.

sdk.stop()

Stops listening to blockchain events.

sdk.subscribe(eventType, filter, callback)

subscribes KeyAdded or KeyRemoved event.

Parameters:
  • eventType : string - a type of an event, possible event types: KeyAdded, KeyRemoved
  • filter : object - a filter for events, includes:
    • contractAddress : string - an address of a contract to observe
    • key : string - a public key used to subscribe to an event
  • callback
Returns:
event listener
Example:
const filter = {
  contractAddress: '0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA',
  key: '0xbA03ea3517ddcD75e38a65EDEB4dD4ae17D52A1A'
};
const subscription = sdk.subscribe(
  'KeyAdded',
  filter,
  (keyInfo) => {
    console.log(`${keyInfo.key} was added.`);
  }
);

Result

0xbA03ea3517ddcD75e38a65EDEB4dD4ae17D52A1A was added

subscription.remove()

removes subscription

Example:
const subscription = sdk.subscribe(
  'KeyAdded',
  filter,
  (keyInfo) => {
    subscription.remove();
  }
);
Authorisations

sdk.subscribeAuthorisations(walletContractAddress, privateKey, callback)

subscribes AuthorisationChanged event

Parameters:
  • walletContractAddress : string - an address of a contract to observe
  • privateKey : string - a private key used to sign a get authorization request
  • callback
Returns:
unsubscribe function
Example:
const unsubscribe = sdk.subscribe(
  '0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA',
  '0x5c8b9227cd5065c7e3f6b73826b8b42e198c4497f6688e3085d5ab3a6d520e74',
  (authorisations) => {
    console.log(`${authorisations}`);
    unsubscribe();
  }
);

Result

[{deviceInfo:
    {
      ipAddress: '89.67.68.130',
      browser: 'Safari',
      city: 'Warsaw'
    },
  id: 1,
  walletContractAddress: '0xA193E42526F1FEA8C99AF609dcEabf30C1c29fAA',
  key: ''}]

Relayer

This documentation is DEPRECATED.

Some descriptions might be invalid.

A relayer is a RESTful JSON API server written in node.js and express.js that allows interacting with a wallet contract using meta-transactions. The relayer gets a signed message and propagates it to the network. It pays for transactions and gets the refund from contracts.

Below are the instructions on how to run the relayer.

If you would like to use your own domain, jump to the section ENS registration.

Starting a relayer
Prerequisites

Database

To run a relayer in a development mode and to run tests you need to have Postgres installed and running. You also need to have universal_login_relayer_development database created.

You can do it in your favorite database UI, or using psql:

psql -c "create database universal_login_relayer_development;"

Factory contract

To run a relayer you also need to deploy your own factory contract, with the same wallet that the relayer will have. To do that you will need a wallet master contract address (you can deploy your own or use ours). To deploy the factory contract run:

universal-login deploy:factory [walletMasterAddress] --privateKey 'YOUR_PRIVATE_KEY' --nodeUrl 'JSON-RPC URL'
Example
universal-login deploy:factory 0xfb152D3b3bB7330aA52b2504BF5ed1f376B1C189 --privateKey 'YOUR_PRIVATE_KEY' --nodeUrl https://ropsten.infura.io
From command line

To start a relayer from the command line, clone UniversalLoginSDK github repository and follow steps:

1. Setup environment

Create .env file in /universal-login-relayer directory and fill up .env file with parameters:

  • JSON_RPC_URL : string - JSON-RPC URL of an Ethereum node
  • PORT : number - a relayer endpoint
  • PRIVATE_KEY : string - a private key of a relayer wallet
  • ENS_ADDRESS : string - an address of an ENS contract
  • ENS_DOMAINS : string - a list of names of a domain
  • WALLET_MASTER_ADDRESS : string - WalletMaster contract address
  • FACTORY_ADDRESS : string - Factory contract address

example .env file

JSON_RPC_URL='https://ropsten.infura.io'
PORT=3311
PRIVATE_KEY='YOUR_PRIVATE_KEY'
ENS_ADDRESS='0x112234455c3a32fd11230c42e7bccd4a84e02010'
ENS_DOMAINS=poppularapp.test,unilogin.test
WALLET_MASTER_ADDRESS='0xfb152D3b3bB7330aA52b2504BF5ed1f376B1C189'
FACTORY_ADDRESS='0xE316A2134F6c2BE3eeFdAde5518ce3F685af27E7'

2. Run relayer

Run the following command from universal-login-relayer directory

yarn relayer:start

Indices and tables