BTC
ETH
HTX
SOL
BNB
View Market
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt

Connect to the decentralized oracle machine Chainlink to feed prices and develop a DeFi call option trading platform example

Chainlink
特邀专栏作者
2020-11-17 05:17
This article is about 11317 words, reading the full article takes about 17 minutes
This article will teach you how to use the Chainlink price feed oracle to develop a simple call option DeFi trading platform with Solidity on the Ethereum mainnet.
AI Summary
Expand
This article will teach you how to use the Chainlink price feed oracle to develop a simple call option DeFi trading platform with Solidity on the Ethereum mainnet.

The broad category of DeFi includes many smart contract application scenarios, such asblockchain votingLiquidity miningLiquidity miningChainlink price feedChainlink price feedOracle uses Solidity to develop a simple call option DeFi trading platform on the Ethereum mainnet. Of course, you can also slightly modify this example to develop a put option trading platform. This platform has a powerful function, that is, all value transfers are carried out through smart contracts, and both parties to the transaction can directly conduct transactions bypassing the middle party. Therefore, this process does not involve any third parties, only smart contracts and decentralized Chainlink price feeds, which are the most typical DeFi applications. Developing a decentralized options trading platform will cover the following:

  • Compare strings in Solidity

  • converts an integer to a fixed number of decimals

  • Create and initialize a token interface, such as LINK

  • Transfer tokens between users/smart contracts

  • Approve token transfer

  • SafeMath

  • Smart contract ABI interface

  • Execute transaction state with require()

  • Ethereum msg.Value and its difference from token value transactions

  • Convert between int and uint

  • payable address

  • Finally, use the Chainlink data aggregator interface to obtain DeFi price data

andGitHubandRemixCheck out the relevant code. Before we officially start, let's briefly introduce what an option contract is. Options contracts give you the option to execute a trade at an agreed price by a certain date. Specifically, if the content of the option contract is to buy assets such as stocks or tokens, it is called a call option. In addition, the sample code in this article can be modified slightly to put options. A put option is just the opposite of a call option, and its content is not to buy an asset but to sell it. The following are some proper nouns related to options:

  • Strike price: the agreed buying/selling price of the asset

  • Option Fee: The fee paid to the seller when buying the contract

  • Expiration Date: The time when the contract ends

  • Strike: The act of a buyer exercising his right to buy or sell an asset at the strike price

Whether you are developing a call or a put option, you need the basic elements of imports, constructors, and global variables.

pragma solidity ^0.6.7;

import "https://github.com/smartcontractkit/chainlink/blob/develop/evm-contracts/src/v0.6/interfaces/LinkTokenInterface.sol";

import "https://github.com/smartcontractkit/chainlink/blob/master/evm-contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/SafeMath.sol";

contract chainlinkOptions {

// overflow safe operator

using SafeMath for uint;

// price feed interface

AggregatorV3Interface internal ethFeed;

AggregatorV3Interface internal linkFeed;

//LINK token interface

LinkTokenInterface internal LINK;

uint ethPrice;

uint linkPrice;

//Precompute string hash value

bytes32 ethHash = keccak256(abi.encodePacked("ETH"));

bytes32 linkHash = keccak256(abi.encodePacked("LINK"));

address payable contractAddr;

//Options are stored in the form of a structure array

struct option {

uint strike; //Price in USD (18 decimal places) option allows buyer to purchase tokens at

uint premium; //Fee in contract token that option writer charges

uint expiry; //Unix timestamp of expiration time

uint amount; //Amount of tokens the option contract is for

bool exercised; //Has option been exercised

bool canceled; //Has option been canceled

uint id; //Unique ID of option, also array index

uint latestCost; //Helper to show last updated cost to exercise

address payable writer; //Issuer of option

address payable buyer; //Buyer of option

}

option[] public ethOpts;

option[] public linkOpts;

//Kovan feed price: https://docs.chain.link/docs/reference-contracts

constructor() public {

// Kovan price feed for ETH/USD

ethFeed = AggregatorV3Interface(0x9326BFA02ADD2366b30bacB125260Af641031331);

//LINK/USD Kovan feed price

linkFeed = AggregatorV3Interface(0x396c5E36DD0a0F5a5D33dae44368D4193f69a1F0);

//Link token address on Kovan

LINK = LinkTokenInterface(0xa36085F69e2889c224210F603D836748e7dC0088);

contractAddr = payable(address(this));

}

When importing, we need to access Chainlink’s data aggregator interface to realize the price feed function, and access the LINK token interface (Note: Here we need to use LINK to transfer money, so we need to use the ERC20 function of the token contract). Finally, we importSafeMath by OpenZeppelinContracts, which are standard library operations that perform built-in overflow checking, while Solidity does not include overflow checking in its built-in operators.

Chainlink price feed

Chainlink price feed

//Return the latest LINK price

function getLinkPrice() public view returns (uint) {

(

uint80 roundID,

int price,

uint startedAt,

uint timeStamp,

uint80 answeredInRound

) = linkFeed.latestRoundData();

//If this round has not ended yet, timestamp is 0

require(timeStamp > 0, "Round not complete");

//The price will never be negative, so you can convert int to uint

//The price has 8 digits after the decimal point, and then needs to add 10 digits to become 18 digits.

return uint(price);

}

first level title

Write a call option contract

//Allow users to write held call options

// Received token type, exercise price (token is denominated in US dollars, with 18 digits after the decimal point), option fee (same as the token’s decimal point), expiration date (unix), token in the contract quantity

function writeOption(string memory token, uint strike, uint premium, uint expiry, uint tknAmt) public payable {

bytes32 tokenHash = keccak256(abi.encodePacked(token));

require(tokenHash == ethHash || tokenHash == linkHash, "Only ETH and LINK tokens are supported");

updatePrices();

if (tokenHash == ethHash) {

require(msg.value == tknAmt, "Incorrect amount of ETH supplied");

uint latestCost = strike.mul(tknAmt).div(ethPrice.mul(10**10)); //Exercise cost denominated in ether, decimal point adjustment

ethOpts.push(option(strike, premium, expiry, tknAmt, false, false, ethOpts.length, latestCost, msg.sender, address(0)));

} else {

require(LINK.transferFrom(msg.sender, contractAddr, tknAmt), "Incorrect amount of LINK supplied");

uint latestCost = strike.mul(tknAmt).div(linkPrice.mul(10**10));

linkOpts.push(option(strike, premium, expiry, tknAmt, false, false, linkOpts.length, latestCost, msg.sender, address(0)));

}

}

image description


Write a LINK option contract for a LINK token, set the Unix expiration time, the exercise price is $10, and the option fee is 0.1 LINK.

first level title

Contract ABI interface

When you view the contract on Etherscan, there will be two tabs, namely: Read Contract and Write Contract. You can use these two tabs to interact with the contract. For example: LINK token main network contract. Etherscan knows what these functions are and how to call them via the contract's ABI. Use the JSON format to call the ABI and specify the function call parameters. It can be called directly on the main network, but the LINK contract on Kovan needs to import this module. you can inLinkTokenimage description

image description

first level title

buy call options

//To buy a call option, you need token, option ID and payment

function buyOption(string memory token, uint ID) public payable {

bytes32 tokenHash = keccak256(abi.encodePacked(token));

require(tokenHash == ethHash || tokenHash == linkHash, "Only ETH and LINK tokens are supported");

updatePrices();

if (tokenHash == ethHash) {

require(!ethOpts[ID].canceled && ethOpts[ID].expiry > now, "Option is canceled/expired and cannot be bought");

//Buyer pays option premium

require(msg.value == ethOpts[ID].premium, "Incorrect amount of ETH sent for premium");

//The seller receives the option fee

ethOpts[ID].writer.transfer(ethOpts[ID].premium);

ethOpts[ID].buyer = msg.sender;

} else {

require(!linkOpts[ID].canceled && linkOpts[ID].expiry > now, "Option is canceled/expired and cannot be bought");

//Transfer option premium from buyer to seller

require(LINK.transferFrom(msg.sender, linkOpts[ID].writer, linkOpts[ID].premium), "Incorrect amount of LINK sent for premium");

linkOpts[ID].buyer = msg.sender;

}

}

first level title

exercise option

//Exercise call option, need token, option ID and payment

function exercise(string memory token, uint ID) public payable {

//If the option has not expired and has not been exercised, the option owner is allowed to exercise

//To exercise the option, the buyer needs to pay the seller the amount of exercise price * quantity, and get the number of tokens agreed in the contract

bytes32 tokenHash = keccak256(abi.encodePacked(token));

require(tokenHash == ethHash || tokenHash == linkHash, "Only ETH and LINK tokens are supported");

if (tokenHash == ethHash) {

require(ethOpts[ID].buyer == msg.sender, "You do not own this option");

require(!ethOpts[ID].exercised, "Option has already been exercised");

require(ethOpts[ID].expiry > now, "Option is expired");

// meet the conditions, make payment

updatePrices();

// Exercise fee

uint exerciseVal = ethOpts[ID].strike*ethOpts[ID].amount;

//Connect to Chainlink to feed the price and convert it into Ethereum

uint equivEth = exerciseVal.div(ethPrice.mul(10**10)); //Convert the 8 decimal places of the feed price to 18

//The buyer pays the ether equivalent to the strike price*quantity to exercise the option.

require(msg.value == equivEth, "Incorrect LINK amount sent to exercise");

//Pay the exercise fee to the seller

ethOpts[ID].writer.transfer(equivEth);

//Pay the buyer the contract amount of ether

msg.sender.transfer(ethOpts[ID].amount);

ethOpts[ID].exercised = true;

} else {

require(linkOpts[ID].buyer == msg.sender, "You do not own this option");

require(!linkOpts[ID].exercised, "Option has already been exercised");

require(linkOpts[ID].expiry > now, "Option is expired");

updatePrices();

uint exerciseVal = linkOpts[ID].strike*linkOpts[ID].amount;

uint equivLink = exerciseVal.div(linkPrice.mul(10**10));

//The buyer exercises the option and pays the exercise fee to the seller

require(LINK.transferFrom(msg.sender, linkOpts[ID].writer, equivLink), "Incorrect LINK amount sent to exercise");

//Pay the contract amount of LINK tokens to the seller

require(LINK.transfer(msg.sender, linkOpts[ID].amount), "Error: buyer was not paid");

linkOpts[ID].exercised = true;

}

}

image description

Example: The result of Remix output when the transaction does not meet one or more conditions.

If the conditions are met, the exercise fee will be paid to the seller, and the contract number of tokens will be paid to the buyer. When exercising the option, the buyer needs to purchase each token at the exercise price. However, the strike price is denominated in USD, while the contract size is denominated in Ether or LINK. Therefore, we need to access the Chainlink price feed to calculate the amount of ETH or LINK equivalent to the exercise fee. After converting to equivalent ETH or LINK, we can start the transfer. When transferring money, you need to use the method mentioned above, that is, the ether currency will call the msg.value/address.transfer function, and LINK will call the transferFrom() function.

first level title

Cancel contract/delete funds

// Allow sellers to cancel contracts or get money back from options that did not successfully close the trade.

function cancelOption(string memory token, uint ID) public payable {

bytes32 tokenHash = keccak256(abi.encodePacked(token));

require(tokenHash == ethHash || tokenHash == linkHash, "Only ETH and LINK tokens are supported");

if (tokenHash == ethHash) {

require(msg.sender == ethOpts[ID].writer, "You did not write this option");

// must not have been canceled or purchased

require(!ethOpts[ID].canceled && ethOpts[ID].buyer == address(0), "This option cannot be canceled");

ethOpts[ID].writer.transfer(ethOpts[ID].amount);

ethOpts[ID].canceled = true;

} else {

require(msg.sender == linkOpts[ID].writer, "You did not write this option");

require(!linkOpts[ID].canceled && linkOpts[ID].buyer == address(0), "This option cannot be canceled");

require(LINK.transferFrom(address(this), linkOpts[ID].writer, linkOpts[ID].amount), "Incorrect amount of LINK sent");

linkOpts[ID].canceled = true;

}

}

//Allow sellers to redeem funds from expired, outstanding, and non-cancelled options.

function retrieveExpiredFunds(string memory token, uint ID) public payable {

bytes32 tokenHash = keccak256(abi.encodePacked(token));

require(tokenHash == ethHash || tokenHash == linkHash, "Only ETH and LINK tokens are supported");

if (tokenHash == ethHash) {

require(msg.sender == ethOpts[ID].writer, "You did not write this option");

//Must be expired, not exercised and not cancelled.

require(ethOpts[ID].expiry <= now && !ethOpts[ID].exercised && !ethOpts[ID].canceled, "This option is not eligible for withdraw");

ethOpts[ID].writer.transfer(ethOpts[ID].amount);

//Modify the cancel flag to true to avoid multiple redemptions

ethOpts[ID].canceled = true;

} else {

require(msg.sender == linkOpts[ID].writer, "You did not write this option");

require(linkOpts[ID].expiry <= now && !linkOpts[ID].exercised && !linkOpts[ID].canceled, "This option is not eligible for withdraw");

require(LINK.transferFrom(address(this), linkOpts[ID].writer, linkOpts[ID].amount), "Incorrect amount of LINK sent");

linkOpts[ID].canceled = true;

}

}

As the market fluctuates, sellers may cancel the option contract and redeem their funds if the option has not been sold. Likewise, if an option expires without being exercised, the seller will certainly want to redeem the funds in the contract. Therefore, we added the cancelOption() and retrieveExpiredFunds() functions

The most critical point of these two functions is that the redemption conditions must be met to call successfully. Sellers must meet certain conditions in order to redeem funds, and can only redeem once. The seller cannot cancel the contract that has been sold, so we need to confirm that the buyer's address is still the initial value 0. In addition, we also need to confirm that the option has not been cancelled, and then refund. If the funds are redeemed after the option expires, the situation will be slightly different. In this case, the option may have been sold but not exercised and the funds should still be returned to the seller. We want to confirm that the contract has expired and has not been exercised. Then also set the option's cancel flag to true to make a refund if the condition is met.

I hope this article helps you develop Chainlink use cases on mainnet right away, and gives you an understanding of Solidity's unique capabilities. If you want to learn more about Chainlink features, check outChainlink VRFChainlink Fair Ordering ServiceChainlink Fair Ordering Service, learn how Chainlink solves the miner frontrunning problem.

and join us atdeveloper documentationand join us atDiscordcontact us.click herecontact us.

Chainlink
DeFi
开发者
期权
Welcome to Join Odaily Official Community