Built on top of the priceless contracts developed by UMA.
Requirements for manual setup
We strongly recommend using Nix to manage the system dependencies, so you can skip to setting up Nix below.
However, if you prefer to not set up Nix, you need to install the following:
- Git
- Node.js LTS (currently v14) (later versions may also work, but that's not verified)
- Yarn (could be installed via npm:
npm install -g yarn
or though your OS' package manager) - Python 3
- Docker
- Docker Compose
Please consult the manual of your system package manager for instructions on installing the software listed above.
-
Clone the project:
Via SSH (if you have a GitLab account and you have added your SSH keys to it )
git clone git@gitlab.com:jarvis-network/apps/exchange/mono-repo.git
Via HTTPS
git clone https://gitlab.com/jarvis-network/apps/exchange/mono-repo.git
-
Build the project:
-
Enter the development shell:
-
If you have
direnv
installed, you just need to allow it to evaluate the.envrc
file in the current folder:direnv allow .
-
If you don't have
direnv
installed (or you prefer not to use it), run:nix develop
-
-
Install
Node.js
dependencies viayarn
(npm
is not supported):yarn
-
jarvis-network/exchange/mono-repo/
├── libs/ | source code | libraries (on which the applications depend)
│ ├── contracts/ | source code | Synthereum Solidity implementation
│ ├── core-utils/ | source code | @jarvis-network/core-utils - core utility library
│ └── hardhat-utils/ | source code | @jarvis-network/hardhat-utils
│
├── scripts/ | scripts | development utility scripts
│ ├── check_commit_range.bash
│ ├── ci_test_affected.bash
│ ├── prepare-release.bash
│ └── repo-cleanup-post-part-1-of-nx.dev-changes.bash
│
├── README.md | docs | this file
├── LICENSE | docs | MIT license
├── docs/ | docs
│ ├── security-audits/ | docs
│ ├── fee-calculations.md | docs
│ └── install-nix.md | docs
│
├── .envrc | nix | direnv config file; takes care of sourcing nix env variables
├── flake.nix | nix | Nix Flake file
├── flake.lock | nix | Nix Flake lock file
├── shell.nix | nix | Nix build-inputs (system dependencies) file
│
├── .npmrc | yarn | npm publish config
├── package.json | yarn | workspace root package.json
├── yarn.lock | yarn | lock file
├── .yarnrc | yarn | config file
│
├── nx.json | nx | (mono repo dev tools) config
├── workspace.json | nx
│
├── docker-bake.hcl | docker | Docker Buildx config
├── Dockerfile | docker | Dockerfile containing all image targets
├── .dockerignore | docker | Docker ignore file
│
├── .gitignore | git | list of files ignored by git
├── CODEOWNERS | gitlab | GitLab merge request approvals config
├── .gitlab-ci.yml | gitlab | config for GitLab CI/CD
│
├── netlify.toml | netlify | URL redirects and rewrites
│
├── commitlint.config.js | code style | commit message lint config
├── .editorconfig | code style | general editor config
├── .eslintrc.json | code style | ESLint
├── .prettierrc | code style | Prettier
├── .prettierignore | code style | Prettier
│
├── jest.config.js | jest | Jest test framework config
├── jest.preset.js | jest
│
├── tsconfig.base.json | typescript | base TypeScript config (shared by the whole repo)
├── tsconfig.cli.json | typescript | config for CLI programs (inherits the base one)
├── tsconfig.frontend.json | typescript | config for frontend apps (inherits the base one)
└── tsconfig.node.json | typescript | config for Node.js apps (inherits the base one)
Running applications or scripts may require setting environment variables.
In addition to exporting environnement variables using the shell, they can
also be set persistently in an .env
file located in the respective app / script
folder. In each folder, where this is relevant there is an .env.example
file that lists the variables that are recognized by the script.
.env
files are ignored by git, so they suitable for setting secrets for
development purposes, such as private keys.
The tasks of building, testing and migrating the contracts are managed by a Hardhat config file located here: libs/contracts/hardhat.config.ts
Migration scripts (used for deploying the contracts) can be invoked from the libs/contracts
folder like so:
yarn migrate-<migration script> --network <network name>
For example:
yarn migrate-finder --network kovan
Use NPM to install new dependencies in the root project directory. Smart contracts can import these dependencies and the compiler will resolve the correct path.
E.g. import "@openzeppelin/contracts/math/SafeMath.sol";
SynFiat uses Truffle and therefore all the standard Truffle commands are available:
truffle console
truffle compile
truffle migrate
truffle test
A front-end client created with create-react-app that uses React and Web3 can be developed in the ./client
directory.
Run npm install
in the client directory to install the client dependencies.
All new client dependencies must be installed here and not in the root project directory.
Run npm run start
to start a local web server for client development. The server defaults to port 3000 and will watch for code changes.
There are a few automated tests written for the TIC. These are helpful to run to ensure there are no regressions introduced when working on the contract.
Tests must be run on a fork of the Kovan network.
- Forking Kovan is necessary because the tests depend on deployed contracts for DAI, rDAI, and the ExpiringMultiPartyCreator. The rDAI address must also be on the ExpiringMultiPartyCreator whitelist.
- Creating a fork of the Kovan network with Ganache is easy. For example, if using Infura as the Ethereum client, run
ganache-cli -f https://kovan.infura.io/v3/{Infura ID}
. - To use the included Ganache script
./run-ganache.sh
, set the following environment variables:ETH_WALLET_MNEMONIC
The 12 word seed phrase for the accounts used to test and deploy the SynFiat smart contracts.ETH_KOVAN_ENDPOINT
The endpoint for the Ethereum client connected to Kovan. E.g.https://kovan.infura.io/v3/${INFURA_ID}
Truffle must be configured to create at least 2 accounts for the network used to run the tests.
- For example, if using the HDWallet provider, the 4th argument specifying the number of accounts to make must be set to 2:
HDWalletProvider(mnemonic, kovanEndpoint, 0, 2)
.
The 2 accounts used to run tests must be funded with ETH and DAI before the network is forked.
- To obtain Kovan ETH (KETH) use the Kovan faucet.
- To obtain DAI, either purchase some with KETH on Oasis (make sure you set MetaMask to the Kovan network) or follow these instructions to set up a CDP and mint DAI.
- Remember, it is important that the DAI contract is the same one that is used by the whitelisted rDAI contract.
- You can find a list of all the Kovan DAI contract addresses that will work with the whitelisted rDAI contract here.
To run the TIC tests, simply use the following Truffle command:
truffle --network kovan-fork test
The SynFiat migration scripts in ./migrations
will only deploy properly when used with either the Kovan network or a fork of Kovan.
To deploy the SynFiat smart contracts with the migration scripts, run the following Truffle command:
truffle --network kovan migrate
If the contracts have already been deployed but changes have been made and they must be deployed again, use the --reset
flag.
Derivative parameters can be changed in ./tic-config.json
. The following parameters are used:
expirationTimestamp
: Time that the derivative expires.disputeBondPct
: Percent of a liquidation position's locked collateral to be deposited by a potential disputer.sponsorDisputeRewardPct
: Percent of price paid to sponsor in the Disputed state (i.e. following a successful dispute).disputerDisputeRewardPct
: Percent of price paid to disputer in the Disputed state (i.e. following a successful dispute).
E.g.
{
"expirationTimestamp": 1590969600,
"disputeBondPct": { "rawValue": "1500000000000000000" },
"sponsorDisputeRewardPct": { "rawValue": "500000000000000000" },
"disputerDisputeRewardPct": { "rawValue": "400000000000000000" }
}
New synthetic assets can be created by modifying ./synthetic-assets.json
before running the migration scripts.
Simply add a new object to the JSON array in ./synthetic-assets.json
. The following parameters are used:
syntheticName
: The name which describes the new token.syntheticSymbol
: The ticker abbreviation of the name.priceFeedIdentifier
: Unique identifier for DVM price feed ticker.collateralRequirement
: The collateral ratio required to prevent liquidationstartingCollateralization
: The collateral to token ratio used when the global ratio is zero
E.g.
[
...
{
"syntheticName": "Jarvis Synthetic Euro",
"syntheticSymbol": "jEUR",
"priceFeedIdentifier": "EUR/USD",
"collateralRequirement": { "rawValue": "1100000000000000000" },
"startingCollateralization": "1300000000000000000"
}
]
Now when running truffle --network kovan migrate --reset
, this new synthetic asset will also be deployed.
First import the TICFactory
Truffle artifact stored in ./client/src/contracts
and create the contract instance with web3.
import TICFactory from "./contracts/TICFactory.json";
const networkId = await web3.eth.net.getId();
const factory = new web3.eth.Contract(TICFactory.abi, TICFactory.networks[networkId].address);
The factory is then used to retrieve the TIC for the synthetic asset you wish to interact with.
import TIC from "./contracts/TIC.json";
// ...
const ticAddress = await factory.methods.symbolToTIC("jEUR").call();
const tic = new web3.eth.Contract(TIC.abi, ticAddress);
Create the DAI contract instance. This is needed to approve the DAI transfers that will be made by the TIC.
import IERC20 from "./contracts/IERC20.json";
// ...
const daiAddress = await tic.collateralToken();
const dai = new web3.eth.Contract(IERC20.abi, daiAddress);
Create the synthetic token contract instance. This is the token minted by the TIC for the user.
const syntheticTokenAddress = await tic.methods.syntheticToken().call();
const syntheticToken = new web3.eth.Contract(IERC20.abi, syntheticTokenAddress);
Finally get the account addresses we will use to interact with the contracts.
const accounts = await web3.eth.getAccounts();
We will assume that accounts[0]
is the liquidity provider.
Set the amount of collateral to deposit.
const collateralAmount = web3.utils.toWei("10");
Approve the transfer of DAI collateral.
await dai.methods.approve(tic.address, collateralAmount).send({ from: accounts[0] });
Deposit the DAI collateral.
await tic.methods.deposit(collateralAmount).send({ from: accounts[0] });
We will assume that accounts[1]
is the user.
Set the amount of collateral used to mint tokens.
const collateralAmount = web3.utils.toWei("10");
Set the number of tokens we will try to mint with the collateral.
const numTokens = web3.utils.toWei("100");
Calculate the fees for the user.
const fees = await tic.methods.calculateMintFee(collateralAmount).call();
Calculate the total amount of DAI the user will need to transfer (collateral plus fees).
const totalToTransfer = web3.utils.toBN(collateralAmount).add(web3.utils.toBN(fees));
Approve the transfer of DAI.
await dai.methods.approve(tic.address, totalToTransfer).send({ from: accounts[1] });
Mint the jEUR tokens.
await tic.methods.mint(collateralAmount, numTokens).send({ from: accounts[1] });
View the jEUR balance.
const jeurBalance = await syntheticToken.methods.balanceOf(accounts[1]).call();
console.log(web3.utils.fromWei(jeurBalance.toString()));
Set the amount of jEUR tokens to transfer. In this case we will transfer all the tokens owned by a user.
const jeurToTransfer = await syntheticToken.methods.balanceOf(accounts[1]).call();
Transfer the jEUR tokens to accounts[2]
.
await derivative.methods.transfer(accounts[2], jeurToTransfer).send({ from: accounts[1] });
Set the amount of jEUR tokens to redeem. In this case we will redeem all the tokens owned by a user.
const jeurToTransfer = await syntheticToken.methods.balanceOf(accounts[1]).call();
Approve the transfer of jEUR tokens.
await syntheticToken.methods.approve(tic.options.address, jeurBalance).send({ from: accounts[1] });
Redeem the user's jEUR tokens.
await tic.methods.settleExpired().send({ from: accounts[1] });
We will assume that accounts[0]
is the liquidity provider.
Set the amount of collateral to withdraw. Note that if a LP tries to withdraw enough collateral to undercollateralize the contract, they will be at risk of liquidation.
const excessCollateral = web3.utils.toWei("5");
Submit a withdraw request.
await tic.methods.withdrawRequest(excessCollateral).send({ from: accounts[0] });
If the request is not disputed during the withdrawal liveness period, the request can be fulfilled.
await tic.methods.withdrawPassedRequest({ from: accounts[0] });
Withdraw the collateral to the liquidity provider's account.
await tic.methods.withdraw(excessCollateral).send({ from: accounts[0] });
Set the amount of source tokens to swap.
const numTokens = web3.utils.toWei("10");
Set the amount of destination tokens to receive.
const destNumTokens = web3.utils.toWei("10");
Get the destination TIC you wish to swap tokens with.
const otherTICAddress = await factory.methods.symbolToTIC("jGBP");
const otherTIC = new web3.eth.Contract(TIC.abi, otherTICAddress);
Approve the transfer of the source tokens.
await syntheticToken.methods.approve(tic.address, numTokens).send({ from: accounts[1] });
Perform the atomic swap of tokens.
await tic.exchange(otherTIC.address, numTokens, destNumTokens);