Maker Protocol Technical Docs
  • MakerDAO Technical Docs
  • Getting Started
    • Maker Protocol 101
  • Smart Contract Modules
    • Dai Module
      • Dai - Detailed Documentation
    • Core Module
      • Vat - Detailed Documentation
      • Spot - Detailed Documentation
    • Collateral Module
      • Join - Detailed Documentation
    • Liquidation 2.0 Module
    • System Stabilizer Module
      • Flapper - Detailed Documentation
      • Flopper - Detailed Documentation
      • Vow - Detailed Documentation
    • Oracle Module
      • Oracle Security Module (OSM) - Detailed Documentation
      • Median - Detailed Documentation
    • MKR Module
    • Governance Module
      • Spell - Detailed Documentation
      • Pause - Detailed Documentation
      • Chief - Detailed Documentation
    • Rates Module
      • Pot - Detailed Documentation
      • Jug - Detailed Documentation
    • Proxy Module
      • Proxy Actions - Detailed Documentation
      • Vote Proxy - Detailed Documentation
      • CDP Manager - Detailed Documentation
      • DSR Manager - Detailed Documentation
    • Flash Mint Module
    • Maker Protocol Emergency Shutdown
      • Emergency Shutdown for Partners
      • The Emergency Shutdown Process for Multi-Collateral Dai (MCD)
      • End - Detailed Documentation
      • ESM - Detailed Documentation
  • Glossary
    • MCD Glossaries
    • Smart Contract Annotations
  • Deployment Addresses
    • Maker Protocol Deployments
  • Security
    • Security for the Maker Protocol
  • Building on top of the Maker Protocol
    • Developer Guides and Tutorials
    • The Dai Javascript Library of the Maker Protocol
      • Getting started
      • Configuration
        • Plugins
      • Vault manager
      • Collateral types
      • Dai Savings Rate
      • Currency units
      • System data
      • Advanced
        • Transaction manager
        • DSProxy
        • Events
        • Using multiple accounts
        • Adding a new service
      • Single-Collateral Sai
        • Collateralized Debt Position
        • CDP Service
        • Price Service
        • System Status
        • Tokens
        • Token Conversion
        • Exchange Service
    • Pymaker
  • Keepers
    • The Auctions of the Maker Protocol
    • Auction Keepers
      • Auction Keeper Bot Setup Guide
    • Market Maker Keepers
      • Market Maker Keeper Bot Setup Guide
    • Cage Keeper
    • Simple Arbitrage Keeper
    • Chief Keeper
  • Command-line Interfaces
    • Seth
    • Multi Collateral Dai (MCD) CLI
    • Dai and Collateral Redemption during Emergency Shutdown
    • Emergency Shutdown (ES) CLI
  • Miscellaneous
    • Liquidations 1.2 System (Deprecated)
      • Cat - Detailed Documentation
      • Flipper - Detailed Documentation
    • SCD <> MCD Migration
    • Upgrading to Multi-Collateral Dai Guide
Powered by GitBook
On this page
  • Summary
  • Steps to add a new service
  • Service Lifecycle
  • Adding Custom Events
Export as PDF
  1. Building on top of the Maker Protocol
  2. The Dai Javascript Library of the Maker Protocol
  3. Advanced

Adding a new service

PreviousUsing multiple accountsNextSingle-Collateral Sai

Last updated 5 years ago

Summary

You can take advantage of the pluggable architecture of this library by choosing different implementations for services, and/or adding new service roles altogether. A service is just a Javascript class that inherits from either PublicService, PrivateService, or LocalService, and contains public method(s).

It can depend on other services through our built-in , and can also be configured through the Maker config file / config options.

Steps to add a new service

The information below is written for developers who want to add to dai.js, but you can also add services through the use of .

Here are the steps to add a new service called ExampleService:

(1) In the src directory, create an ExampleService.js file in one of the subdirectories.

(2) In src/config/DefaultServiceProvider.js, import ExampleService.js and add it to the _services array

(3) Create a class called ExampleService in ExampleService.js

(4) The service must extend one of:

  • PrivateService - requires both a network connection and authentication

  • PublicService - requires just a network connection

  • LocalService - requires neither

See the Service Lifecycle section below for more info

(5) In the constructor, call the parent class's constructor with the following arguments:

  • The name of the service. This is how the service can be referenced by other services

  • An array of the names of services to depend on

(6) Add the necessary public methods

//example code in ExampleService.js for steps 3-6
import PublicService from '../core/PublicService';

export default class ExampleService extends PublicService {
    constructor (name='example') {
        super(name, ['log']);
    }

    test(){
        this.get('log').info('test');
    }

(7) If your service will be used to replace a default service (the full list of default service roles can be found in src/config/ConfigFactory), then skip this step. Otherwise, you'll need to add your new service role (e.g. "example") to the ServiceRoles array in src/config/ConfigFactory.

(8) Create a corresponding ExampleService.spec.js file in the test directory. Write a test in the test file that creates a Maker object using your service.

//example code in ExampleService.spec.js for step 8
import Maker from '../../src/index';

//step 8: a new service role ('example') is used
test('test 1', async () => {
  const maker = await Maker.create('http', {example: "ExampleService"});
  const exampleService = customMaker.service('example');
  exampleService.test(); //logs "test"
});

//step 8: a custom service replaces a default service (Web3)
test('test 2', async () => {
  const maker = await Maker.create('http', {web3: "MyCustomWeb3Service"});
  const mycustomWeb3Service = maker.service('web3');
});

(9) (Optional) Implement the relevant service lifecycle functions (initialize(), connect(), and authenticate()). See the Service Lifecycle section below for more info

(10) (Optional) Allow for configuration. Service-specific settings can be passed into a service by the Maker config file or config options. These service-specific settings can then be accessed from inside a service as the parameter passed into the initialize function (see the Service Lifecycle section below)

//step 10: in ExampleService.spec.js
const maker = await Maker.create('http', {
    example: ["ExampleService", {
    exampleSetting: "this is a configuration setting"
  }]
});

//step 10: accessing configuration settings in ExampleService.js
initialize(settings) {
  if(settings.exampleSetting){
    this.get('log').info(settings.exampleSetting);
  }
}

Service Lifecycle

The three kinds of services mentioned in step 4 above follow the following state machine diagrams in the picture below.

To specify what initializing, connecting and authenticating entails, implement the initialize(), connect(), and authenticate() functions in the service itself. This will be called while the service's manager brings the service to the corresponding state.

//example initialize() function in ExampleService.js
  initialize(settings) {
    this.get('log').info('ExampleService is initializing...');
    this._setSettings(settings);
  }

A service will not finish initializing/connecting/authenticating until all of its dependent services have completed the same state (if applicable - for example a LocalService is considered authenticated/connected in addition to initialized, if it has finished initializing). The example code here shows how to wait for the service to be in a certain state.

const maker = await Maker.create('http', {example: "ExampleService"});
const exampleService = customMaker.service('example');

//wait for example service and its dependencies to initialize
await exampleService.manager().initialize();

//wait for example service and its dependencies to connect
await exampleService.manager().connect();

//wait for example service and its dependencies to authenticate
await exampleService.manager().authenticate();

//can also use callback syntax
exampleService.manager().onConnected(()=>{
    /*executed after connected*/
});

//wait for all services used by the maker object to authenticate
maker.authenticate();

Adding Custom Events

One way to add an event is to “register” a function that gets called on each new block, using the event service's registerPollEvents() function. For example, here is some code from the price service. this.getEthPrice() will be called on each new block, and if the state has changed from the last call, a price/ETH_USD event will be emitted with the payload { price: [new_price] }.

Another way to an add an event is to manually emit an event using the event service's emit function. For example, when the Web3Service initializes, it emits an event that contains info about the provider.

Note that calling registerPollEvents and emit() directly on the event service like in the previous two examples will register events on the "default" event emitter instance. However, you can create a new event emitter instance for your new service. For example, the CDP object defines it's own event emitter, as can be seen here, by calling the event service's buildEmitter() function.

//in PriceService.js
this.get('event').registerPollEvents({
      'price/ETH_USD': {
        price: () => this.getEthPrice()
      }
    });

//in Web3Service.js
this.get('event').emit('web3/INITIALIZED', {
  provider: { ...settings.provider }
});

//in the constructor in the Cdp.js
this._emitterInstance = this._cdpService.get('event').buildEmitter();
this.on = this._emitterInstance.on;
this._emitterInstance.registerPollEvents({
  COLLATERAL: {
    USD: () => this.getCollateralValueInUSD(),
    ETH: () => this.getCollateralValueInEth()
  },
  DEBT: {
    dai: () => this.getDebtValueInDai()
  }
});
dependency injection framework
plugins