Skip to content

Instantly share code, notes, and snippets.

@Ashar2shahid
Last active April 9, 2023 19:13
Show Gist options
  • Save Ashar2shahid/6da449b873d1b45e1e27ee01645c54a1 to your computer and use it in GitHub Desktop.
Save Ashar2shahid/6da449b873d1b45e1e27ee01645c54a1 to your computer and use it in GitHub Desktop.
pragma solidity 0.8.17;
import "@api3/contracts/v0.8/interfaces/IProxy.sol";
contract Api3Options {
// ETH/USD proxy address
address proxy;
uint ethPrice;
address payable contractAddr;
//Options stored in arrays of structs
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 writer; //Issuer of option
address buyer; //Buyer of option
}
option[] public ethOpts;
constructor(address _proxy) public {
//Set the ETH/USD proxy address
proxy = _proxy;
contractAddr = payable(address(this));
}
//Returns the latest ETH price
function getEthPrice() public view returns (uint) {
(int224 value,uint32 timestamp) = IProxy(proxy).read();
// if the data feed is being updated with a one day-heartbeat
// interval, you may want to check for that.
require(
timestamp + 1 days > block.timestamp,
"Timestamp older than one day"
);
//Price should never be negative thus cast int to unit is ok
//Price is 18 decimal places
return uint(uint224(value));
}
//Updates prices to latest
function updatePrices() internal {
ethPrice = getEthPrice();
}
//Allows user to write a covered call option
//Takes which token, a strike price(USD per token w/18 decimal places), premium(same unit as token), expiration time(unix) and how many tokens the contract is for
function writeOption(address ethProxy, uint strike, uint premium, uint expiry, uint tknAmt) public payable {
require(ethProxy == proxy, "Only ETH tokens are supported");
updatePrices();
require(msg.value == tknAmt, "Incorrect amount of ETH supplied");
uint latestCost = (strike * (tknAmt)) / (ethPrice); //current cost to exercise in ETH, decimal places corrected
ethOpts.push(option(strike, premium, expiry, tknAmt, false, false, ethOpts.length, latestCost, msg.sender, address(0)));
}
//Allows option writer to cancel and get their funds back from an unpurchased option
function cancelOption(address ethProxy, uint ID) public payable {
require(ethProxy == proxy, "Only ETH tokens are supported");
require(msg.sender == ethOpts[ID].writer, "You did not write this option");
//Must not have already been canceled or bought
require(!ethOpts[ID].canceled && ethOpts[ID].buyer == address(0), "This option cannot be canceled");
payable(ethOpts[ID].writer).transfer(ethOpts[ID].amount);
ethOpts[ID].canceled = true;
}
//Purchase a call option, needs desired token, ID of option and payment
function buyOption(address ethProxy, uint ID) public payable {
require(ethProxy == proxy, "Only ETH tokens are supported");
updatePrices();
require(!ethOpts[ID].canceled && ethOpts[ID].expiry > block.timestamp, "Option is canceled/expired and cannot be bought");
//Transfer premium payment from buyer
require(msg.value == ethOpts[ID].premium, "Incorrect amount of ETH sent for premium");
//Transfer premium payment to writer
payable(ethOpts[ID].writer).transfer(ethOpts[ID].premium);
ethOpts[ID].buyer = msg.sender;
}
//Exercise your call option, needs desired token, ID of option and payment
function exercise(address ethProxy, uint ID) public payable {
//If not expired and not already exercised, allow option owner to exercise
//To exercise, the strike value*amount equivalent paid to writer (from buyer) and amount of tokens in the contract paid to buyer
require(ethProxy == proxy, "Only ETH tokens are supported");
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 > block.timestamp, "Option is expired");
//Conditions are met, proceed to payouts
updatePrices();
//Cost to exercise
uint exerciseVal = ethOpts[ID].strike*ethOpts[ID].amount;
//Equivalent ETH value using Chainlink feed
uint equivEth = exerciseVal / (ethPrice); //move decimal 10 places right to account for 8 places of pricefeed
//Buyer exercises option by paying strike*amount equivalent ETH value
require(msg.value == equivEth, "Incorrect LINK amount sent to exercise");
//Pay writer the exercise cost
payable(ethOpts[ID].writer).transfer(equivEth);
//Pay buyer contract amount of ETH
payable(msg.sender).transfer(ethOpts[ID].amount);
ethOpts[ID].exercised = true;
}
//Allows writer to retrieve funds from an expired, non-exercised, non-canceled option
function retrieveExpiredFunds(address ethProxy, uint ID) public payable {
require(ethProxy == proxy, "Only ETH tokens are supported");
require(msg.sender == ethOpts[ID].writer, "You did not write this option");
//Must be expired, not exercised and not canceled
require(ethOpts[ID].expiry <= block.timestamp && !ethOpts[ID].exercised && !ethOpts[ID].canceled, "This option is not eligible for withdraw");
payable(ethOpts[ID].writer).transfer(ethOpts[ID].amount);
//Repurposing canceled flag to prevent more than one withdraw
ethOpts[ID].canceled = true;
}
//This is a helper function to help the user see what the cost to exercise an option is currently before they do so
//Updates lastestCost member of option which is publicly viewable
function updateExerciseCost(address ethProxy, uint ID) public {
require(ethProxy == proxy, "Only ETH tokens are supported");
updatePrices();
ethOpts[ID].latestCost = ethOpts[ID].strike * (ethOpts[ID].amount) / (ethPrice);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment