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.


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

npm i web3 @universal-login/web3 react react-dom

If you are using yarn than type:

yarn add @universal-login/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.isUniversalLogin;

Starting with SDK


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

npm i @universal-login/sdk

If you are using yarn than type:

yarn add @universal-login/sdk

Development environment

Before running the development environment, make sure you have PostgreSQL installed, up and running.

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

With npm:

npm install @universal-login/ops --save-dev

With yarn:

yarn add --dev @universal-login/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:

  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 '@universal-login/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 '@universal-login/sdk';
import ethers from 'ethers';

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

const universalLoginSDK = new UniversalLoginSDK(relayerUrl, jsonRpcUrl);

Start listening for events

Then make UniversalLoginSDK start listening for relayer and blockchain events


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',

const subscription = sdk.subscribe(
  (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




Install the universal-login toolkit:

yarn global add @universal-login/ops

Test token

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


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]

  • 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


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]
  • 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 0x314159265dd8dbb310642f98f50c066173c1259b --privateKey 'YOUR_PRIVATE_KEY' --nodeUrl 'https://mainnet.infura.io' --gasPrice 11000000000


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]
  • my-domain - a domain to register
  • publicResolverAddress : string - the address of a public resolver. For the Ropsten test network a working public resolver address is 0x4C641FB9BAd9b60EF180c31F56051cE826d21A9A and for the Rinkeby test network a public resolver address is 0x5d20cf83cb385e06d2f2a892f9322cd4933eacdc.
  • 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 0x112234455c3a32fd11230c42e7bccd4a84e02010 --privateKey 'YOUR_PRIVATE_KEY' --nodeUrl 'https://ropsten.infura.io'


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.


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

new DomainRegistrar(config)

creates DomainRegistrar.

  • 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
DomainRegistrar instance
import {providers, Wallet} from 'ethers';

const ensInfo = {
  ensAddress: '0x112234455c3a32fd11230c42e7bccd4a84e02010',
  publicResolverAddress: '0x4C641FB9BAd9b60EF180c31F56051cE826d21A9A'
const provider = new providers.JsonRpcProvider('https://ropsten.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)

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

file named extra-domain.test_info that includes: