Writing an integration is a process that requires writing data that describes assets that use that integration and code to deal with fetching price and generating transactions to enter/exit a position. In general terms, these steps are fundamental:
Devising a name for the asset's type
Adding asset descriptions
Adding ABIs for all relevant contracts
Writing a concrete InterfaceStrategy for the integration
Writing end-to-end tests
Some optional actions:
Writing scripts to populate and update assets according to external sources
Writing unit tests for individual parts of the integration
Create a class for new asset type
The main element in order to create an integration supporting a new asset type is a class, namely a strategy to deal with all operations that involve that asset type.
The structure of this class is specified in InterfaceStrategy. The stub of an implementation looks like this:
The expected return is a RequestTree object that returns Promises for all data that needs to be fetched in order to calculate that asset's price.
Example #1: asset that depends on a linked asset
An asset that merely depends on the price data of the underlying asset can do so using fetchPriceData passing the same provider and assetStore, but specifying the linked asset.
In other words, this asset doesn't need to fetcch any data on its own, but delegates it down to its linked asset.
// This examples is from the AaveV2 integrationfetchPriceData({ provider, assetStore, asset }: FetchPriceDataParams) {constlinkedAsset=assetStore.getAssetById(asset.linkedAssets[0].assetId);constrequestTree:RequestTree=fetchPriceData({ provider, assetStore, asset: linkedAsset, });return requestTree;}
Example #2: asset that needs fetching some data
Another case is an asset that needs to fetch some data about itself in order to subsequently calculate the price.
// This example is from the Beefy integrationfetchPriceData({ provider, assetStore, asset }: FetchPriceDataParams) {constlinkedAsset=assetStore.getAssetById(asset.linkedAssets[0].assetId);constpool=newContract(asset.address, IBeefyVaultV6, provider);let requestTree:RequestTree= {}; requestTree[asset.address] = {}; requestTree[asset.address].underlyingAmount= () =>pool.balance(); requestTree[asset.address].supply= () =>pool.totalSupply();constfetchedData=fetchPriceData({ provider, assetStore, asset: linkedAsset, }); requestTree = {...requestTree,...fetchedData, };return requestTree;}
In the above example. the requestTree will be resolved to get:
underlyingAmount from the pool (invoked using its balance function)
supply from the pool (invoked using its totalSupply function)
All requestData required by underlying asset.
getPrice
The getPrice function takes three parameters:
assetStore: AssetStore
asset: Asset
requestTree: RequestTree - with the resolved promises for all fetched assets.
The expected return is a number with the price.
Example #1: asset that depends on a linked asset
An asset that depends on the price data of the underlying asset just has to invoke and getPrice for that linked asset.
// This examples is from the AaveV2 integrationgetPrice({ assetStore, asset, requestTree }: GetPriceParams) {constlinkedAsset=assetStore.getAssetById(asset.linkedAssets[0].assetId);returngetPrice({ assetStore, asset: linkedAsset, requestTree });}
Example #2: asset that needed fetching data
If the getPrice requires using data that was requested on the previous step, this data will be available on the requestTree parameter, with the same key (generally asset.address) and key (whatever it was called on fetchPriceData).
// This example is from the Beefy integrationgetPrice({ assetStore, asset, requestTree }: GetPriceParams) {constlinkedAsset=assetStore.getAssetById(asset.linkedAssets[0].assetId);constamount=getAmount({ amount: requestTree[asset.address].underlyingAmount, decimals:linkedAsset.decimals, });constsupply=getAmount({ amount: requestTree[asset.address].supply, decimals:asset.decimals, });return ( (amount *getPrice({ assetStore, asset: linkedAsset, requestTree })) / supply );}
generateStep
This step, the core of the transaction generation, is where information regarding the operation is processed to generate all the steps required. Each step is a call to a smart contract.
These are the steps that will typically be performed:
Get all assets relevant to the operation. This is done using the assetStore.
Determine which stores will be needed. This is done using routerOperation.stores.findOrInitializeStoreIdx.
Evaluate whether the position is being entered or exited. This is done by evaluating the value of assetAllocation.fraction.
Update the estimated allocation for all assets using currentAllocation.updateFraction.
Add steps for each smart contract interation required using routerOperation.addStep.