Navbar:
- Getting-started
- Maker
- CDP
- Price
- cdp-type-parameters
- Exchange
- System-status
- Events
- Transaction
- Accounts
- Proxy-service
- Source-code-setup
- New-service
Dai.js is a JavaScript library that makes it easy to build applications on top of MakerDAO's platform of smart contracts. You can use Maker's contracts to open Collateralized Debt Positions (CDPs), withdraw loans in Dai, trade tokens on OasisDEX, and more. The library features a pluggable, service-based architecture, which allows users maximal control when integrating the Maker functionality into existing infrastructures. It also includes convenient configuration presets for out-of-the-box usability and support for both front-end and back-end applications. Maker's entire suite of contracts will eventually be accessible through this library—including the DAO governance and the upcoming multi-collateral release—but functionality is limited in the current alpha version to the following areas:
- Opening and closing CDPs
- Locking and unlocking collateral
- Withdrawing and repaying Dai
- Automated token conversions
- Token contract functionality for WETH, PETH, MKR, Dai, and ETH
- Buying and selling MKR and Dai with built-in DEX integration
For example code that consumes the library, check out this repository.
Install the package with npm:
npm install @makerdao/dai
Once it's installed, import the module into your project as shown on the right.
import Maker from '@makerdao/dai';
// or:
const Maker = require('@makerdao/dai');
UMD This library is also accessible as a UMD module.
<script src="./dai.js" />
<script>
Maker.create('http',{
privateKey: YOUR_PRIVATE_KEY,
url: 'https://kovan.infura.io/v3/YOUR_INFURA_PROJECT_ID'
})
.then(maker => { window.maker = maker })
.then(() => maker.authenticate())
.then(() => maker.openCdp())
.then(cdp => console.log(cdp.id));
</script>
This library is also accessible as a UMD module.
Using the Maker and Cdp APIs, the code shown on the right opens a cdp, locks 0.25 ETH into it, draws out 50 Dai, and then logs information about the newly opened position to the console.
import Maker from '@makerdao/dai';
async function openLockDraw() {
const maker = await Maker.create("http", {
privateKey: YOUR_PRIVATE_KEY,
url: 'https://kovan.infura.io/v3/YOUR_INFURA_PROJECT_ID'
});
await maker.authenticate();
const cdp = await maker.openCdp();
await cdp.lockEth(0.25);
await cdp.drawDai(50);
const debt = await cdp.getDebtValue();
console.log(debt.toString); // '50.00 DAI'
}
openLockDraw();
When instantiating a Maker
object, you pass in the name of a configuration preset and an options hash.
Available presets are listed below, with the required options for each shown on the right.
'browser'
- Use the provider from the browser (e.g. MetaMask).
'http'
- Use Web3.providers.HttpProvider.
'test'
-
Use a local testnet (e.g. Ganache) running at
http://127.0.0.1:2000
, and sign transactions using testnet-managed keys. -
const makerBrowser = await Maker.create('browser');
const makerHttp = await Maker.create('http', { privateKey: YOUR_PRIVATE_KEY, url: 'https://kovan.infura.io/v3/YOUR_INFURA_PROJECT_ID' });
const makerTest = await Maker.create('test');
-
privateKey
- The private key used to sign transactions. If not provided, the first account available from the Ethereum provider will be used.
url
- The URL of the node to connect to. Only used when
provider.type
is"HTTP"
.
- The URL of the node to connect to. Only used when
provider.type
"TEST"
: connect to a local test blockchain at 'http://127.1:2000'"HTTP"
: connect to an Ethereum node via any arbitrary url
web3.statusTimerDelay
- Number in milliseconds that represents how often the blockchain connection and account authentication is checked. This allows the library to move out of an authenticated or connected state when it discovers it no longer has access to unlocked accounts, or can no longer connect to a node.
- Default value: 5000 (5 seconds)
web3.transactionSettings
- Object containing transaction options to be applied to all transactions sent through the library.
- Default value:
{ gasLimit: 4000000 }
web3.confirmedBlockCount
- Number of blocks to wait after a transaction has been mined when calling
confirm
. See transactions for further explanation. Defaults to 5.
- Number of blocks to wait after a transaction has been mined when calling
log
-
Set this to
false
to reduce the verbosity of logging.const maker = await Maker.create('http', { privateKey: YOUR_PRIVATE_KEY, // '0xabc...' url: 'http://some-ethereum-rpc-node.net', provider: { type: 'HTTP', // or 'TEST' network: 'kovan' }, web3: { statusTimerDelay: 2000, confirmedBlockCount: 8 transactionSettings: { gasPrice: 12000000000 } }, log: false });
-
After creating your Maker instance, and before using any other methods, run this. It makes sure all services are initialized, connected to any remote API's, and properly authenticated.
await maker.authenticate();
Params: service (string)
- Returns: service object
The service function can be used to access services that were injected into your instance of maker
. See the service documentation sections below.
const priceService = maker.service('price');
Params: none
- Returns: promise (resolves to new CDP object once mined)
maker.openCdp()
will create a new CDP, and then return the CDP object, which can be used to access other CDP functionality.
The promise will resolve when the transaction is mined.
const newCdp = await maker.openCdp();
Params: CDP ID (integer)
- Returns: promise (resolves to CDP object)
maker.getCdp(id)
creates a CDP object for an existing CDP. The CDP object can then be used to interact with your CDP.
const cdp = await maker.getCdp(614);
Methods that take numerical values as input can also take instances of token classes that the library provides. These are useful for managing precision, keeping track of units, and passing in wei values. Most methods that return numerical values return them wrapped in one of these classes. There are two types:
- currency units, which represent an amount of a type of currency
- price units, which represent an exchange rate between two currencies.
The classes that begin with USD
are price units; e.g. USD_ETH
represents the price of ETH in USD.
Useful instance methods:
-
toNumber: return the raw JavaScript value. This may fail for very large numbers.
-
toBigNumber: return the raw value as a BigNumber.
-
isEqual: compare the values and symbols of two different instances.
-
toString: show the value in human-readable form, e.g. "500 USD/ETH".
import Maker from '@makerdao/dai'; const { MKR, DAI, ETH, WETH, PETH, USD_ETH, USD_MKR, USD_DAI } = Maker;
// These are all identical:
// each method has a default type cdp.lockEth(0.25); cdp.lockEth('0.25');
// you can pass in a currency unit instance cdp.lockEth(ETH(0.25)); cdp.lockEth(ETH.wei(250000000000000000));
// you can pass the unit as an options argument cdp.lockEth(0.25, { unit: ETH }); cdp.lockEth(250000000000000000, { unit: ETH.wei });
const eth = ETH(5); eth.toString() == '5.00 ETH';
const price = USD_ETH(500); price.toString() == '500.00 USD/ETH';
// multiplication handles units const usd = eth.times(price); usd.toString() == '2500.00 USD';
// division does too const eth2 = usd.div(eth); eth2.isEqual(eth);
Now that you've used maker
to either open a new CDP or retrieve an existing one, you can use the returned cdp
object to call functions on it.
**.id**
This is the ID of the CDP object. You can pass this ID to [Maker.getCdp](https://makerdao.com/documentation/#getcdp)
.
- Params: currency unit (optional)
- Returns: promise (resolves to the amount of outstanding debt)
cdp.getDebtValue()
returns the amount of debt that has been borrowed against the collateral in the CDP. By default it returns the amount of Dai as a currency unit, but can return the equivalent in USD if the first argument is Maker.USD
.
const daiDebt = await cdp.getDebtValue();
const usdDebt = await cdp.getDebtValue(Maker.USD);
- Params: currency unit (optional)
- Returns: promise (resolves to the value of the accrued governance fee in USD)
cdp.getGovernanceFee()
returns the value of the accrued governance fee. By default it returns the amount of MKR as a currency unit, but can return the equivalent in USD if the first argument is Maker.USD
.
Note: this is often referred to as the *Stability Fee*
, even though technically the *Stability Fee*
is the fee that is paid in Dai, and the *Governance Fee*
is the fee that is paid in MKR. But since fees are only paid in MKR in Single-Collateral Dai, and only paid in Dai in Multi-Collateral Dai, the fee in Single-Collateral Dai is often referred to as the *Stability Fee*
to be consistent with the term that will be used in Multi-Collateral Dai and to avoid unduly confusing regular users.
const mkrFee = await cdp.getGovernanceFee();
const usdFee = await cdp.getGovernanceFee(Maker.USD);
Params: none
- Returns: promise (resolves to the collateralization ratio)
cdp.getCollateralizationRatio()
returns the USD value of the collateral in the CDP divided by the USD value of the Dai debt for the CDP, e.g. 2.5
.
const ratio = await cdp.getCollateralizationRatio();
Params: none
- Returns: promise (resolves to the liquidation price)
cdp.getLiquidationPrice()
returns the price of Ether in USD that causes the CDP to become unsafe (able to be liquidated), all other factors constant. It returns a USD_ETH
price unit.
const ratio = await cdp.getLiquidationPrice();
- Params: currency unit (optional)
- Returns: promise (resolves to collateral amount)
cdp.getCollateralValue()
returns the value of the collateral in the CDP. By default it returns the amount of ETH as a currency unit, but can return the equivalent in PETH or USD depending on the first argument.
const ethCollateral = await cdp.getCollateralValue();
const pethCollateral = await cdp.getCollateralValue(Maker.PETH);
const usdCollateral = await cdp.getCollateralValue(Maker.USD);
Params: none
- Returns: promise (resolves to boolean)
cdp.isSafe()
returns true if the cdp is safe, that is, if the USD value of its collateral is greater than or equal to USD value of the its debt multiplied by the liquidation ratio.
const ratio = await cdp.isSafe();
Params: amount of Dai to wipe
- Returns: promise (resolves to boolean)
cdp.enoughMkrToWipe(dai)
returns true if the current account owns enough MKR to wipe the specified amount of Dai from the CDP.
const enoughMkrToWipe = await cdp.enoughMkrToWipe(10000000000000000000, DAI.wei);
- Params: amount to lock in the CDP, in units defined by the price service.
- Returns: promise (resolves to
[transactionObject](https://makerdao.com/documentation/#transactions)
once mined)
cdp.lockEth(eth)
abstracts the token conversions needed to lock collateral in a CDP. It first converts the ETH to WETH, then converts the WETH to PETH, then locks the PETH in the CDP.
Note: this process is not atomic, so it's possible for some of the transactions to succeed but not all three. See Using DsProxy for executing multiple transactions atomically.
return await cdp.lockEth(10000000000000000000, ETH.wei);
// or equivalently
return await cdp.lockEth(100, ETH);
- Params: amount to draw (in Dai, as string)
- Returns: promise (resolves to
[transactionObject](https://makerdao.com/documentation/#transactions)
once mined)
cdp.drawDai(dai)
withdraws the specified amount of Dai as a loan against the collateral in the CDP. As such, it will fail if the CDP doesn't have enough PETH locked in it to remain at least 150% collateralized.
return await cdp.drawDai(10000000000000000000, DAI.wei);
// or equivalently
return await cdp.drawDai(100, DAI);
- Params: amount to repay (in Dai, as string)
- Returns: promise (resolves to
[transactionObject](https://makerdao.com/documentation/#transactions)
once mined)
cdp.wipeDai(dai)
sends Dai back to the CDP in order to repay some (or all) of its outstanding debt.
Note: CDPs accumulate MKR governance debt over their lifetime. This must be paid when wiping dai debt, and thus MKR must be acquired before calling this method.
return await cdp.wipeDai(10000000000000000000, DAI.wei);
// or equivalently
return await cdp.wipeDai(100, DAI);
- Params: amount of Peth collateral to free from the CDP, in units defined by the price service.
- Returns: promise (resolves to
[transactionObject](https://makerdao.com/documentation/#transactions)
once mined)
cdp.freePeth(peth)
withdraws the specified amount of PETH and returns it to the owner's address. As such, the contract will only allow you to free PETH that's locked in excess of 150% of the CDP's outstanding debt.
return await cdp.freePeth(100, PETH);
// or equivalently
return await cdp.freePeth(10000000000000000000, PETH.wei);
Params: Ethereum address (string)
- Returns: promise (resolves to
[transactionObject](https://makerdao.com/documentation/#transactions)
once mined)
cdp.give(address)
transfers ownership of the CDP from the current owner to the address you provide as an argument.
return await cdp.give('0x046ce6b8ecb159645d3a605051ee37ba93b6efcc');
Params: none
- Returns: promise (resolves to
[transactionObject](https://makerdao.com/documentation/#transactions)
once mined)
cdp.shut()
wipes all remaining dai, frees all remaining collateral, and deletes the CDP. This will fail if the caller does not have enough DAI and MKR to wipe all debt.
return await cdp.shut();
Params: none
- Returns: promise (resolves to
[transactionObject](https://makerdao.com/documentation/#transactions)
once mined)
cdp.bite()
will initiate the liquidation process of an undercollateralized CDP
return await cdp.bite();
Retrieve the PriceService through Maker.service('price')
.
The PriceService exposes the collateral and governance tokens' price information that is reported by the oracles in the Maker system.
const price = maker.service('price');
Get the current USD price of ETH, as a USD_ETH
price unit.
const ethPrice = await price.getEthPrice();
Get the current USD price of the governance token MKR, as a USD_MKR
price unit.
const mkrPrice = await price.getMkrPrice();
Get the current USD price of PETH (pooled ethereum), as a USD_PETH
price unit.
await pethPrice = price.getPethPrice();
Set the current USD price of ETH. This requires the necessary permissions and will only be useful in a testing environment.
await price.setEthPrice(475);
Set the current USD price of the governance token MKR. This requires the necessary permissions and will only be useful in a testing environment.
await price.setMkrPrice(950.00);
Returns the current W-ETH to PETH ratio.
await price.getWethToPethRatio();
Retrieve the ETH CDP Service through Maker.service('cdp'). The ETH CDP Service exposes the risk parameter information for the Ether CDP type (in single-collateral Dai, this is the only CDP Type)
const ethCdp = maker.service('cdp');
Params: none
- Returns: promise (resolves to liquidation ratio)
getLiquidationRatio()
returns a decimal representation of the liquidation ratio, e.g. 1.5
const ratio = await ethCdp.getLiquidationRatio();
Params: none
- Returns: promise (resolves to liquidation penalty)
getLiquidationPenalty()
returns a decimal representation of the liquidation penalty, e.g. 0.13
const penalty = await ethCdp.getLiquidationPenalty();
Params: none
- Returns: promise (resolves to yearly governance fee)
getAnnualGovernanceFee()
returns a decimal representation of the annual governance fee, e.g. 0.005.
const fee = await ethCdp.getAnnualGovernanceFee();
Note: this is often referred to as the *Stability Fee*
, even though technically the *Stability Fee*
is the fee that is paid in Dai, and the *Governance Fee*
is the fee that is paid in MKR. But since fees are only paid in MKR in Single-Collateral Dai, and only paid in Dai in Multi-Collateral Dai, the fee in Single-Collateral Dai is often referred to as the *Stability Fee*
to be consistent with the term that will be used in Multi-Collateral Dai and to avoid unduly confusing regular users.
Retrieve the OasisExchangeService (or alternative implementation) through Maker.service('exchange')
.
The exchange service allows to buy and sell DAI, MKR, and other tokens. The default OasisExchangeService implementation uses the OasisDEX OTC market for this.
const exchange = maker.service('exchange');
Sell a set amount of DAI and receive another token in return.
-
Parameters
daiAmount
- Amount of DAI to sell.tokenSymbol
- Token to receive in return.minFillAmount
- Minimum amount to receive in return.
-
Returns: promise (resolves to OasisOrder once mined)
// Sell 100.00 DAI for 0.30 WETH or more. const sellOrder = await exchange.sellDai('100.0', 'WETH', '0.30');
Buy a set amount of DAI and give another token in return.
-
Parameters
daiAmount
- Amount of DAI to buy.tokenSymbol
- Token to give in return.minFillAmount
- Maximum amount to give in return.
-
Returns: promise (resolves to OasisOrder once mined)
// Buy 100.00 DAI for 0.30 WETH or less. const buyOrder = await exchange.buyDai('100.0', 'WETH', '0.35');
OasisOrders
have a few methods: * fillAmount
: amount of token received in exchange * fees()
: amount of ether spent on gas * created()
: timestamp of when transaction was mined
const buyOrder = await exchange.buyDai('100.0', 'WETH', '0.35');
const fillAmount = buyOrder.fillAmount();
const gasPaid = buyOrder.fees();
const created = buyOrder.created();
To access system status information, retrieve the ETH CDP Service through Maker.service('cdp').
const ethCdp = maker.service('cdp');
Params: none
- Returns: promise (resolves to system collateralization ratio)
getSystemCollateralization()
returns the collateralization ratio for the entire system, e.g. 2.75
const systemRatio = await ethCdp.getSystemCollateralization();
Params: none
- Returns: promise (resolves to target price)
getTargetPrice()
returns the target price of Dai in USD, that is, the value to which Dai is soft-pegged, e.g. 1.0
const targetPrice = await ethCdp.getTargetPrice();
The event pipeline allows developers to easily create real-time applications by letting them listen for important state changes and lifecycle events.
Wildcards
An event name passed to any event emitter method can contain a wildcard (the *
character). A wildcard may appear as foo/*
, foo/bar/*
, or simply *
.
*
matches one sub-level.
e.g. price/*
will trigger on both price/USD_ETH
and price/MKR_USD
but not price/MKR_USD/foo
.
**
matches all sub-levels.
e.g. price/**
will trigger on price/USD_ETH
, price/MKR_USD
, and price/MKR_USD/foo
.
Event Object
Triggered events will receive the object shown on the right.
-
<event_type>
- the name of the event -
<event_payload>
- the new state data sent with the event -
<event_sequence_number>
- a sequentially increasing index -
<latest_block_when_emitted>
- the current block at the time of the emit{ type: <event_type>, payload: <event_payload>, /* if applicable */ index: <event_sequence_number>, block: <latest_block_when_emitted> }
**Price**
Event Name | Payload |
---|---|
price/ETH_USD |
{ price } |
price/MKR_USD |
{ price } |
price/WETH_PETH |
{ ratio } |
maker.on('price/ETH_USD', eventObj => {
const { price } = eventObj.payload;
console.log('ETH price changed to', price);
})
**Web3**
Event Name | Payload |
---|---|
web3/INITIALIZED |
{ provider: { type, url } } |
web3/CONNECTED |
{ api, network, node } |
web3/AUTHENTICATED |
{ account } |
web3/DEAUTHENTICATED |
{ } |
web3/DISCONNECTED |
{ } |
maker.on('web3/AUTHENTICATED', eventObj => {
const { account } = eventObj.payload;
console.log('web3 authenticated with account', account);
})
Event Name | Payload |
---|---|
COLLATERAL |
{ USD, ETH } |
DEBT |
{ dai } |
cdp.on('DEBT', eventObj => {
const { dai } = eventObj.payload;
console.log('Your cdp now has a dai debt of', dai);
})
TransactionObject Lifecycle Hooks
The transactionManager
service is used to access and monitor TransactionObjects
which can be used to track a transaction's status as it propagates through the blockchain. TransactionObjects
have methods which trigger callbacks in the event of a transaction's status being pending
, mined
, confirmed
or error
.
const txMgr = maker.service('transactionManager');
// instance of transactionManager
const open = maker.service('cdp').openCdp();
// open is a transactionObject when promise resolves
Listen
Accessing these status callbacks is done through the listen
function from the transactionManager
service. The listen function takes a promise and a callback object with the transaction status as a key.
One caveat is that the confirmed
event will not fire unless the transactionManager
confirms the promise. This confirm()
function waits on a number of blocks after the transaction has been mined, of which the default is 5, to resolve. To change this, the confirmedBlockCount
attribute in the options object can be modified.
txMgr.listen(open, {
pending: tx => {
// do something when tx is pending
},
mined: tx => {
// do something when tx is mined
},
confirmed: tx => {
// do something when tx is confirmed
},
error: tx => {
// do someting when tx fails
}
});
await txMgr.confirm(open); // confirmed will fire after 5 blocks if default
Transaction Metadata
There are functions such as lockEth()
which are composed of several internal transactions. These can be more accurately tracked by accessing the tx.metadata
in the callback which contains both the contract
and the method
the internal transactions were created from.
const lock = cdp.lockEth(1);
txMgr.listen(lock, {
pending: tx => {
const {contract, method} = tx.metadata;
if(contract === 'WETH' && method === 'deposit') {
console.log(tx.hash); // print hash for WETH.deposit
}
}
})
TransactionObject Methods
A TransactionObject
also has a few methods to provide further details on the transaction:
hash
: transaction hashfees()
: amount of ether spent on gastimeStamp()
: timestamp of when transaction was minedtimeStampSubmitted()
: timestamp of when transaction was submitted to the network
The library supports the use of multiple accounts (i.e. private keys) within a single Maker instance. Accounts can be specified in the constructor options or with the addAccount
method.
Call useAccount
to switch to using an account by its name, or useAccountWithAddress
to switch to using an account by its address, and subsequent calls will use that account as the transaction signer.
When the Maker instance is first created, it will use the account named default
if it exists, or the first account in the list otherwise.
const maker = await Maker.create({
url: 'http://localhost:2000',
accounts: {
other: {type: privateKey, key: someOtherKey},
default: {type: privateKey, key: myKey}
}
});
await maker.authenticate();
await maker.addAccount('yetAnother', {type: privateKey, key: thirdKey});
const cdp1 = await maker.openCdp(); // owned by "default"
maker.useAccount('other');
const cdp2 = await maker.openCdp(); // owned by "other"
maker.useAccount('yetAnother');
const cdp3 = await maker.openCdp(); // owned by "yetAnother"
await maker.addAccount({type: privateKey, key: fourthAccount.key}); // the name argument is optional
maker.useAccountWithAddress(fourthAccount.address)
const cdp4 = await maker.openCdp(); //owned by the fourth account
In addition to the privateKey
account type, there are two other built-in types:
-
provider
: Get the first account from the provider (e.g. the value fromgetAccounts
). -
browser
: Get the first account from the provider in the browser (e.g. MetaMask), even if the Maker instance is configured to use a different provider.const maker = await Maker.create({ url: 'http://localhost:2000', accounts: { // this will be the first account from the provider at // localhost:2000 first: {type: 'provider'},
// this will be the current account in MetaMask, and it // will send its transactions through MetaMask second: {type: 'browser'}, // this account will send its transactions through the // provider at localhost:2000 third: {type: 'privateKey', key: myPrivateKey} }
})
Plugins can add additional account types. There are currently two such plugins for hardware wallet support:
-
import TrezorPlugin from '@makerdao/dai-plugin-trezor-web'; import LedgerPlugin from '@makerdao/dai-plugin-ledger-web';
const maker = await Maker.create({ plugins: [ TrezorPlugin, LedgerPlugin, ], accounts: { // default derivation path is "44'/60'/0'/0/0" myTrezor: {type: 'trezor', path: derivationPath1}, myLedger: {type: 'ledger', path: derivationPath2} } });
Install the multiple accounts demo app to see this functionality in action.
The DSProxyService
includes all the functionality necessary for interacting with both types of proxies found in Maker products: profile proxies and forwarding proxies.
Forwarding proxies are simple contracts that aggregate function calls in the body of a single method. These are used in the CDP Portal and Oasis Direct in order to allow users to execute multiple transactions atomically, which is both safer and more user-friendly than implementing several steps as discrete transactions.
// Forwarding proxy
function lockAndDraw(address tub_, bytes32 cup, uint wad) public payable {
lock(tub_, cup);
draw(tub_, cup, wad);
}
Forwarding proxies are meant to be as simple as possible, so they lack some features that could be important if they are to be used as interfaces for more complex smart contract logic. This problem can be solved by using profile proxies (i.e., copies of DSProxy) to execute the functionality defined in the forwarding proxies.
The first time an account is used to interact with any Maker application, the user will be prompted to deploy a profile proxy. This copy of DSProxy can be used in any product, including dai.js, by way of a universal proxy registry. Then, the calldata from any function in the forwarding proxy can be passed to DSProxy's execute()
method, which runs the provided code in the context of the profile proxy.
// Calling the forwarding proxy with dai.js
function lockAndDraw(tubContractAddress, cdpId, daiAmount, ethAmount) {
const saiProxy = maker.service('smartContract').getContractByName('SAI_PROXY');
return saiProxy.lockAndDraw(
tubContractAddress,
cdpId,
daiAmount,
{
value: ethAmount,
dsProxy: true
}
);
}
This makes it possible for users' token allowances to persist from one Maker application to another, and it allows users to recover any funds mistakenly sent to the proxy's address.
Many of the functions in DSProxyService
will only be relevant to power users
. All that is strictly required to automatically generate a function's calldata and find the correct profile proxy is the inclusion of { dsProxy: true }
in the options object for any transaction — provided the user has already deployed a profile proxy. If that's not certain, it may also be necessary to query the registry to determine if a user already owns a proxy, and to build
one if they do not.
- Params: None
- Returns: promise (resolves to address or
null
)
If the currentAccount
(according the Web3Service
) has already deployed a DSProxy, currentProxy()
returns its address. If not, it returns null
. It will update automatically in the event that the active account is changed.
This function should be used to check whether a user has a proxy before attempting to build one.
asnyc function getProxy() {
return maker.service('proxy').currentProxy();
}
- Params: None
- Returns:
TransactionObject
build
will deploy a copy of DSProxy owned by the current account. This transaction will revert if the current account already owns a profile proxy.
By default, build()
returns after the transaction is mined.
async function buildProxy() {
const proxyService = maker.service('proxy');
if (!proxyService.currentProxy()) {
return await proxyService.build();
}
}
This function is designed to be called by the TransactionManager
, not accessed directly. As long as the target contract is defined by the SmartContractService
, the inclusion of the dsProxy
key in the options for any transaction will automatically forward the transaction to the DSProxyService
.
The value of dsProxy
can either be true
or an explicitly provided DSProxy address.
function lockAndDraw(tubContractAddress, cdpId, daiAmount, ethAmount) {
const saiProxy = maker.service('smartContract').getContractByName('SAI_PROXY');
return saiProxy.lockAndDraw(
tubContractAddress,
cdpId,
daiAmount,
{
value: ethAmount,
dsProxy: true
}
);
}
Params: Address (optional)
- Returns: promise (resolves to contract address)
getProxyAddress
will query the proxy registry for the profile proxy address associated with a given account. If no address is provided as a parameter, the function will return the address of the proxy owned by the currentAccount
.
const proxy = await maker.service('proxy').getProxyAddress('0x...');
Params: Address
- Returns: promise (resolves to address)
getOwner
will query the proxy registry for the owner of a provided instance of DSProxy.
const owner = await maker.service('proxy').getOwner('0x...');
- Params: Address of new owner, DSProxy address (optional)
- Returns:
TransactionObject
setOwner
can be used to give a profile proxy to a new owner. The address of the recipient account must be specified, but the DSProxy address will default to currentProxy
if the second parameter is excluded.
async function giveProxy(newOwner, proxyAddress) {
return await maker.service('proxy').setOwner(newOwner, proxyAddress);
}
git clone https://github.com/makerdao/makerdao-integration-poc
npm install
The test suite is configured to run on a Ganache test chain.
If you run the tests with npm test
, it will start a test chain and deploy all the smart contracts for the Dai stablecoin system. This takes a few minutes.
To keep the test chain running and re-run the tests when changes are made to the code, use the command npm run test:watch
.
If you want to deploy the contracts to a test chain without running the test suite, use npm run test:net
.
Start the dev server using npm start
, then open http://localhost:9000.
npm start
- start the dev servernpm run build:backend
- create backend build indist
foldernpm run build:frontend
- create frontend build indist
foldernpm run lint
- run an ESLint checknpm run coverage
- run code coverage and generate report in thecoverage
foldernpm test
- run all testsnpm run test:watch
- run all tests in watch modenpm run test:net
- launch a Ganache test chain and deploy MakerDAO's contracts on it
Note: This section is only for advanced users that are willing to modify the dai.js source code. In the future, you will be able to add custom services without needing to modify the source code.
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 a built-in dependency injection framework, and can also be configured through the Maker config file / config options.
Here are the steps to add a new service called ExampleService
to MakerJS:
-
(1) In the
src
directory, create anExampleService.js
file in one of the subdirectories. -
(2) In
src/config/DefaultServiceProvider.js
, importExampleService.js
and add it to the_services
array -
(3) Create a class called
ExampleService
inExampleService.js
//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'); }
-
(4) The service must
extend
one of:PrivateService
- requires both a network connection and authenticationPublicService
- requires just a network connectionLocalService
- 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.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'); });
-
(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 theServiceRoles
array insrc/config/ConfigFactory
. -
(8) Create a corresponding
ExampleService.spec.js
file in thetest
directory. Write a test in the test file that creates a Maker object using your service. -
(9) (Optional) Implement the relevant service lifecycle functions (
initialize()
,connect()
, andauthenticate()
). See theService Lifecycle
section below for more info//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); } }
-
(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 theService Lifecycle
section below)
The three kinds of services mentioned in step 4 above follow the following state machine diagrams in the picture below.
//example initialize() function in ExampleService.js
initialize(settings) {
this.get('log').info('ExampleService is initializing...');
this._setSettings(settings);
}
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.
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();
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] }.
//in PriceService.js
this.get('event').registerPollEvents({
'price/ETH_USD': {
price: () => this.getEthPrice()
}
});
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.
//in Web3Service.js
this.get('event').emit('web3/INITIALIZED', {
provider: { ...settings.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 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()
}
});