- Change token name to MIME (@gracelyn)
- Create synthetics for sample NFT collection (@dallon) ✅
- Hook FE into contract layer by using Synthetix FE Repo as an example (@jpapi, @dallon)
- Hook in oracle for NFT pricing (@jpapi, @dallon)
To run locally,
- Run
npm install
in root - Run
npx hardhat node
in one terminal to start a local node - Run
node publish build -t && node publish deploy -n local -h --add-new-synths
in a second terminal to build with test flags (required for local dev) and deploy the local node while ensuring new synth contracts will be generated and deployed.
Our goal is to create new Synthetics for NFT collections. We can do that by using the existing MultiCollateralSynth
. Synthetix deploys contracts representing an arbitrary synthetic by specifying the desired synthetic in config files and running a deploy script which generates a contract for that synth as a subclass of MultiCollateralSynth
.
We can use the same mechanism to create a synthetic for a given NFT collection. While we can manually make changes in config files, the most reliable way to do it is a find-and-replace across the whole codebase to swap an existing synthetic name for the desired name of our collection. Find and replace is more reliable because tracking down each relevant file is difficult and causes deploy failures until done thoroughly.
We want to change the chainlink oracle used so we can fetch dynamic collection price data. The current oracles in use are found in ExchangeRates.sol
where Aggregator
and Flag
interfaces are imported. We can make a GET request within an oracle as described here.
An alternative approach to an oracle to fetch price data is to make a FE fetch and pass that result into the BE. This approach would require a nonce to verify request's validity, but the most complexity will likely come from changing flow from contract entrypoint to accommodate new argument passed from FE. So the recommended approach is to make an oracle.
The oracle needs to make a post to our firebase appraisal endpoint by specifying the collection slug in the request and parsing a averagePriceInUsd
string from the JSON response.
Next steps are to make our specified NFT collection synths use our new oracle's method, while making sure existing synths instead use existing oracle methods. But we also need to make sure that the Appraisal API
response includes all necessary data such that the new oracle return value shape matches that of existing oracle methods to facilitate new oracle method integration with the existing system. The former task may be approached by customizing the Synth generation logic in the deploy scripts, while the latter task may require tracking the flow of core methods (eg issueSynths
) to the point that they interact with the existing oracle methods to know the expected return value shapes.
Synthetix is a crypto-backed synthetic asset platform.
It is a multi-token system, powered by SNX, the Synthetix Network Token. SNX holders can stake SNX to issue Synths, on-chain synthetic assets via the Staking dApp The network currently supports an ever growing list of synthetic assets. Please see the list of the deployed contracts on MAIN and TESTNETS Synths can be traded using Kwenta
Synthetix uses a proxy system so that upgrades will not be disruptive to the functionality of the contract. This smooths user interaction, since new functionality will become available without any interruption in their experience. It is also transparent to the community at large, since each upgrade is accompanied by events announcing those upgrades. New releases are managed via the Synthetix Improvement Proposal (SIP) system similar to the EIPs
Prices are committed on chain by a trusted oracle provided by Chainlink.
Please note that this repository is under development.
For the latest system documentation see docs.synthetix.io
For a guide from the community, see synthetix.community
A note on the branches used in this repo.
master
represents the contracts live onmainnet
and all testnets.
When a new version of the contracts makes its way through all testnets, it eventually becomes promoted in master
, with semver reflecting contract changes in the major
or minor
portion of the version (depending on backwards compatibility). patch
changes are simply for changes to the JavaScript interface.
Please see docs.synthetix.io/contracts/testing for an overview of the automated testing methodologies.
This repo may be installed via npm install
to support both node.js scripting applications and Solidity contract development.
💯 Please see our walkthroughs for code examples in both JavaScript and Solidity: docs.synthetix.io/integrations
All interfaces are available via the path synthetix/contracts/interfaces
.
âš¡ In your code, the key is to use IAddressResolver
which can be tied to the immutable proxy: ReadProxyAddressResolver
(introduced in SIP-57). You can then fetch Synthetix
, FeePool
, Depot
, et al via IAddressResolver.getAddress(bytes32 name)
where name
is the bytes32
version of the contract name (case-sensitive). Or you can fetch any synth using IAddressResolver.getSynth(bytes32 synth)
where synth
is the bytes32
name of the synth (e.g. iETH
, sUSD
, sDEFI
).
E.g.
npm install synthetix
then you can write Solidity as below (using a compiler that links named imports via node_modules
):
pragma solidity 0.5.16;
import 'synthetix/contracts/interfaces/IAddressResolver.sol';
import 'synthetix/contracts/interfaces/ISynthetix.sol';
contract MyContract {
// This should be instantiated with our ReadProxyAddressResolver
// it's a ReadProxy that won't change, so safe to code it here without a setter
// see https://docs.synthetix.io/addresses for addresses in mainnet and testnets
IAddressResolver public synthetixResolver;
constructor(IAddressResolver _snxResolver) public {
synthetixResolver = _snxResolver;
}
function synthetixIssue() external {
ISynthetix synthetix = synthetixResolver.getAddress('Synthetix');
require(synthetix != address(0), 'Synthetix is missing from Synthetix resolver');
// Issue for msg.sender = address(MyContract)
synthetix.issueMaxSynths();
}
function synthetixIssueOnBehalf(address user) external {
ISynthetix synthetix = synthetixResolver.getAddress('Synthetix');
require(synthetix != address(0), 'Synthetix is missing from Synthetix resolver');
// Note: this will fail if `DelegateApprovals.approveIssueOnBehalf(address(MyContract))` has
// not yet been invoked by the `user`
synthetix.issueMaxSynthsOnBehalf(user);
}
}
getAST({ source, match = /^contracts\// })
Returns the Abstract Syntax Tree (AST) for all compiled sources. Optionally addsource
to restrict to a single contract source, and setmatch
to an empty regex if you'd like all source ASTs including third party contractsgetPathToNetwork({ network, file = '' })
Returns the path to the folder (or file within the folder) for the given networkgetSource({ network })
Returnabi
andbytecode
for a contractsource
getSuspensionReasons({ code })
Return mapping ofSystemStatus
suspension codes to string reasonsgetStakingRewards({ network })
Return the list of staking reward contracts available.getSynths({ network })
Return the list of synths for a networkgetTarget({ network })
Return the information about a contract'saddress
andsource
file. The contract names are those specified in docs.synthetix.io/addressesgetTokens({ network })
Return the list of tokens (synths andSNX
) used in the system, along with their addresses.getUsers({ network })
Return the list of user accounts within the Synthetix protocol (e.g.owner
,fee
, etc)getVersions({ network, byContract = false })
Return the list of deployed versions to the network keyed by tagged version. IfbyContract
istrue
, it keys bycontract
name.networks
Return the list of supported networkstoBytes32
Convert any string to abytes32
value
const snx = require('synthetix');
snx.getAST();
/*
{ 'contracts/AddressResolver.sol':
{ imports:
[ 'contracts/Owned.sol',
'contracts/interfaces/IAddressResolver.sol',
'contracts/interfaces/ISynthetix.sol' ],
contracts: { AddressResolver: [Object] },
interfaces: {},
libraries: {} },
'contracts/Owned.sol':
{ imports: [],
contracts: { Owned: [Object] },
interfaces: {},
libraries: {} },
*/
snx.getAST({ source: 'Synthetix.sol' });
/*
{ imports:
[ 'contracts/ExternStateToken.sol',
'contracts/MixinResolver.sol',
'contracts/interfaces/ISynthetix.sol',
'contracts/TokenState.sol',
'contracts/interfaces/ISynth.sol',
'contracts/interfaces/IERC20.sol',
'contracts/interfaces/ISystemStatus.sol',
'contracts/interfaces/IExchanger.sol',
'contracts/interfaces/IIssuer.sol',
'contracts/interfaces/ISynthetixState.sol',
'contracts/interfaces/IExchangeRates.sol',
'contracts/SupplySchedule.sol',
'contracts/interfaces/IRewardEscrow.sol',
'contracts/interfaces/IHasBalance.sol',
'contracts/interfaces/IRewardsDistribution.sol' ],
contracts:
{ Synthetix:
{ functions: [Array],
events: [Array],
variables: [Array],
modifiers: [Array],
structs: [],
inherits: [Array] } },
interfaces: {},
libraries: {} }
*/
// Get the path to the network
snx.getPathToNetwork({ network: 'mainnet' });
//'.../Synthetixio/synthetix/publish/deployed/mainnet'
// retrieve an object detailing the contract ABI and bytecode
snx.getSource({ network: 'rinkeby', contract: 'Proxy' });
/*
{
bytecode: '0..0',
abi: [ ... ]
}
*/
snx.getSuspensionReasons();
/*
{
1: 'System Upgrade',
2: 'Market Closure',
3: 'Circuit breaker',
99: 'Emergency',
};
*/
// retrieve the array of synths used
snx.getSynths({ network: 'rinkeby' }).map(({ name }) => name);
// ['sUSD', 'sEUR', ...]
// retrieve an object detailing the contract deployed to the given network.
snx.getTarget({ network: 'rinkeby', contract: 'ProxySynthetix' });
/*
{
name: 'ProxySynthetix',
address: '0x322A3346bf24363f451164d96A5b5cd5A7F4c337',
source: 'Proxy',
link: 'https://rinkeby.etherscan.io/address/0x322A3346bf24363f451164d96A5b5cd5A7F4c337',
timestamp: '2019-03-06T23:05:43.914Z',
txn: '',
network: 'rinkeby'
}
*/
// retrieve the list of system user addresses
snx.getUsers({ network: 'mainnet' });
/*
[ { name: 'owner',
address: '0xEb3107117FEAd7de89Cd14D463D340A2E6917769' },
{ name: 'deployer',
address: '0xDe910777C787903F78C89e7a0bf7F4C435cBB1Fe' },
{ name: 'marketClosure',
address: '0xC105Ea57Eb434Fbe44690d7Dec2702e4a2FBFCf7' },
{ name: 'oracle',
address: '0xaC1ED4Fabbd5204E02950D68b6FC8c446AC95362' },
{ name: 'fee',
address: '0xfeEFEEfeefEeFeefEEFEEfEeFeefEEFeeFEEFEeF' },
{ name: 'zero',
address: '0x0000000000000000000000000000000000000000' } ]
*/
snx.getVersions();
/*
{ 'v2.21.12-107':
{ tag: 'v2.21.12-107',
fulltag: 'v2.21.12-107',
release: 'Hadar',
network: 'kovan',
date: '2020-05-08T12:52:06-04:00',
commit: '19997724bc7eaceb902c523a6742e0bd74fc75cb',
contracts: { ReadProxyAddressResolver: [Object] }
}
}
*/
snx.networks;
// [ 'local', 'kovan', 'rinkeby', 'ropsten', 'mainnet' ]
snx.toBytes32('sUSD');
// '0x7355534400000000000000000000000000000000000000000000000000000000'
Same as above but as a CLI tool that outputs JSON, using names without the get
prefixes:
$ npx synthetix ast contracts/Synth.sol
{
"imports": [
"contracts/Owned.sol",
"contracts/ExternStateToken.sol",
"contracts/MixinResolver.sol",
"contracts/interfaces/ISynth.sol",
"contracts/interfaces/IERC20.sol",
"contracts/interfaces/ISystemStatus.sol",
"contracts/interfaces/IFeePool.sol",
"contracts/interfaces/ISynthetix.sol",
"contracts/interfaces/IExchanger.sol",
"contracts/interfaces/IIssue"
# ...
]
}
$ npx synthetix bytes32 sUSD
0x7355534400000000000000000000000000000000000000000000000000000000
$ npx synthetix networks
[ 'local', 'kovan', 'rinkeby', 'ropsten', 'mainnet' ]
$ npx synthetix source --network rinkeby --contract Proxy
{
"bytecode": "0..0",
"abi": [ ... ]
}
$ npx synthetix suspension-reason --code 2
Market Closure
$ npx synthetix synths --network rinkeby --key name
["sUSD", "sEUR", ... ]
$ npx synthetix target --network rinkeby --contract ProxySynthetix
{
"name": "ProxySynthetix",
"address": "0x322A3346bf24363f451164d96A5b5cd5A7F4c337",
"source": "Proxy",
"link": "https://rinkeby.etherscan.io/address/0x322A3346bf24363f451164d96A5b5cd5A7F4c337",
"timestamp": "2019-03-06T23:05:43.914Z",
"network": "rinkeby"
}
$ npx synthetix users --network mainnet --user oracle
{
"name": "oracle",
"address": "0xaC1ED4Fabbd5204E02950D68b6FC8c446AC95362"
}
$ npx synthetix versions
{
"v2.0-19": {
"tag": "v2.0-19",
"fulltag": "v2.0-19",
"release": "",
"network": "mainnet",
"date": "2019-03-11T18:17:52-04:00",
"commit": "eeb271f4fdd2e615f9dba90503f42b2cb9f9716e",
"contracts": {
"Depot": {
"address": "0x172E09691DfBbC035E37c73B62095caa16Ee2388",
"status": "replaced",
"replaced_in": "v2.18.1"
},
"ExchangeRates": {
"address": "0x73b172756BD5DDf0110Ba8D7b88816Eb639Eb21c",
"status": "replaced",
"replaced_in": "v2.1.11"
},
# ...
}
}
}
$ npx synthetix versions --by-contract
{
"Depot": [
{
"address": "0x172E09691DfBbC035E37c73B62095caa16Ee2388",
"status": "replaced",
"replaced_in": "v2.18.1"
},
{
"address": "0xE1f64079aDa6Ef07b03982Ca34f1dD7152AA3b86",
"status": "current"
}
],
"ExchangeRates": [
{
"address": "0x73b172756BD5DDf0110Ba8D7b88816Eb639Eb21c",
"status": "replaced",
"replaced_in": "v2.1.11"
},
# ...
],
# ...
}