diff --git a/README.md b/README.md
index b8907a9d..633eb0ab 100644
--- a/README.md
+++ b/README.md
@@ -17,8 +17,9 @@ Please check out existing EIPs, such as [EIP-1](eip-0001.md), to understand the
| [EIP-0020](eip-0020.md) | ErgoPay Protocol |
| [EIP-0021](eip-0021.md) | Genuine tokens verification |
| [EIP-0022](eip-0022.md) | Auction Contract |
+| [EIP-0023](eip-0023/eip-0023.md) | Oracle pool 2.0 |
| [EIP-0024](eip-0024.md) | Artwork contract |
| [EIP-0025](eip-0025.md) | Payment Request URI |
| [EIP-0027](eip-0027.md) | Emission Retargeting Soft-Fork |
| [EIP-0031](eip-0031.md) | Babel Fees |
-| [EIP-0039](eip-0039.md) | Monotonic box creation height rule |
\ No newline at end of file
+| [EIP-0039](eip-0039.md) | Monotonic box creation height rule |
diff --git a/eip-0023/contracts/ballot_contract.es b/eip-0023/contracts/ballot_contract.es
new file mode 100644
index 00000000..7e74a613
--- /dev/null
+++ b/eip-0023/contracts/ballot_contract.es
@@ -0,0 +1,32 @@
+{ // This box (ballot box):
+ // R4 the group element of the owner of the ballot token [GroupElement]
+ // R5 the creation height of the update box [Int]
+ // R6 the value voted for [Coll[Byte]]
+ // R7 the reward token id [Coll[Byte]]
+ // R8 the reward token amount [Long]
+
+ val updateNFT = fromBase64("YlFlVGhXbVpxNHQ3dyF6JUMqRi1KQE5jUmZValhuMnI=") // TODO replace with actual
+
+ val minStorageRent = 10000000L // TODO replace with actual
+
+ val selfPubKey = SELF.R4[GroupElement].get
+
+ val outIndex = getVar[Int](0).get
+ val output = OUTPUTS(outIndex)
+
+ val isSimpleCopy = output.R4[GroupElement].isDefined && // ballot boxes are transferable by setting different value here
+ output.propositionBytes == SELF.propositionBytes &&
+ output.tokens == SELF.tokens &&
+ output.value >= minStorageRent
+
+ val update = INPUTS.size > 1 &&
+ INPUTS(1).tokens.size > 0 &&
+ INPUTS(1).tokens(0)._1 == updateNFT && // can only update when update box is the 2nd input
+ output.R4[GroupElement].get == selfPubKey && // public key is preserved
+ output.value >= SELF.value && // value preserved or increased
+ ! (output.R5[Any].isDefined) // no more registers; prevents box from being reused as a valid vote
+
+ val owner = proveDlog(selfPubKey)
+
+ owner || (isSimpleCopy && update)
+}
diff --git a/eip-0023/contracts/oracle_contract.es b/eip-0023/contracts/oracle_contract.es
new file mode 100644
index 00000000..4606531e
--- /dev/null
+++ b/eip-0023/contracts/oracle_contract.es
@@ -0,0 +1,49 @@
+{ // This box (oracle box)
+ // R4 public key (GroupElement)
+ // R5 epoch counter of current epoch (Int)
+ // R6 data point (Long) or empty
+
+ // tokens(0) oracle token (one)
+ // tokens(1) reward tokens collected (one or more)
+ //
+ // When publishing a datapoint, there must be at least one reward token at index 1
+ //
+ // We will connect this box to pool NFT in input #0 (and not the refresh NFT in input #1)
+ // This way, we can continue to use the same box after updating pool
+ // This *could* allow the oracle box to be spent during an update
+ // However, this is not an issue because the update contract ensures that tokens and registers (except script) of the pool box are preserved
+
+ // Private key holder can do following things:
+ // 1. Change group element (public key) stored in R4
+ // 2. Store any value of type in or delete any value from R4 to R9
+ // 3. Store any token or none at 2nd index
+
+ // In order to connect this oracle box to a different refreshNFT after an update,
+ // the oracle should keep at least one new reward token at index 1 when publishing data-point
+
+ val poolNFT = fromBase64("RytLYlBlU2hWbVlxM3Q2dzl6JEMmRilKQE1jUWZUalc=") // TODO replace with actual
+
+ val otherTokenId = INPUTS(0).tokens(0)._1
+
+ val minStorageRent = 10000000L
+ val selfPubKey = SELF.R4[GroupElement].get
+ val outIndex = getVar[Int](0).get
+ val output = OUTPUTS(outIndex)
+
+ val isSimpleCopy = output.tokens(0) == SELF.tokens(0) && // oracle token is preserved
+ output.propositionBytes == SELF.propositionBytes && // script preserved
+ output.R4[GroupElement].isDefined && // output must have a public key (not necessarily the same)
+ output.value >= minStorageRent // ensure sufficient Ergs to ensure no garbage collection
+
+ val collection = otherTokenId == poolNFT && // first input must be pool box
+ output.tokens(1)._1 == SELF.tokens(1)._1 && // reward tokenId is preserved (oracle should ensure this contains a reward token)
+ output.tokens(1)._2 > SELF.tokens(1)._2 && // at least one reward token must be added
+ output.R4[GroupElement].get == selfPubKey && // for collection preserve public key
+ output.value >= SELF.value && // nanoErgs value preserved
+ ! (output.R5[Any].isDefined) // no more registers; prevents box from being reused as a valid data-point
+
+ val owner = proveDlog(selfPubKey)
+
+ // owner can choose to transfer to another public key by setting different value in R4
+ isSimpleCopy && (owner || collection)
+}
diff --git a/eip-0023/contracts/pool_contract.es b/eip-0023/contracts/pool_contract.es
new file mode 100644
index 00000000..01f243c8
--- /dev/null
+++ b/eip-0023/contracts/pool_contract.es
@@ -0,0 +1,16 @@
+{
+ // This box (pool box)
+ // epoch start height is stored in creation Height (R3)
+ // R4 Current data point (Long)
+ // R5 Current epoch counter (Int)
+ //
+ // tokens(0) pool token (NFT)
+ // tokens(1) reward tokens
+ // When initializing the box, there must be one reward token. When claiming reward, one token must be left unclaimed
+
+ val otherTokenId = INPUTS(1).tokens(0)._1
+ val refreshNFT = fromBase64("VGpXblpyNHU3eCFBJUQqRy1LYU5kUmdVa1hwMnM1djg=") // TODO replace with actual
+ val updateNFT = fromBase64("YlFlVGhXbVpxNHQ3dyF6JUMqRi1KQE5jUmZValhuMnI=") // TODO replace with actual
+
+ sigmaProp(otherTokenId == refreshNFT || otherTokenId == updateNFT)
+}
diff --git a/eip-0023/contracts/refresh_contract.es b/eip-0023/contracts/refresh_contract.es
new file mode 100644
index 00000000..cdd49628
--- /dev/null
+++ b/eip-0023/contracts/refresh_contract.es
@@ -0,0 +1,76 @@
+{ // This box (refresh box)
+ //
+ // tokens(0) refresh token (NFT)
+
+ val oracleTokenId = fromBase64("KkctSmFOZFJnVWtYcDJzNXY4eS9CP0UoSCtNYlBlU2g=") // TODO replace with actual
+ val poolNFT = fromBase64("RytLYlBlU2hWbVlxM3Q2dzl6JEMmRilKQE1jUWZUalc=") // TODO replace with actual
+ val epochLength = 30 // TODO replace with actual
+ val minDataPoints = 4 // TODO replace with actual
+ val buffer = 4 // TODO replace with actual
+ val maxDeviationPercent = 5 // percent // TODO replace with actual
+
+ val minStartHeight = HEIGHT - epochLength
+ val spenderIndex = getVar[Int](0).get // the index of the data-point box (NOT input!) belonging to spender
+
+ val poolIn = INPUTS(0)
+ val poolOut = OUTPUTS(0)
+ val selfOut = OUTPUTS(1)
+
+ def isValidDataPoint(b: Box) = if (b.R6[Long].isDefined) {
+ b.creationInfo._1 >= minStartHeight && // data point must not be too old
+ b.tokens(0)._1 == oracleTokenId && // first token id must be of oracle token
+ b.R5[Int].get == poolIn.R5[Int].get // it must correspond to this epoch
+ } else false
+
+ val dataPoints = INPUTS.filter(isValidDataPoint)
+ val pubKey = dataPoints(spenderIndex).R4[GroupElement].get
+
+ val enoughDataPoints = dataPoints.size >= minDataPoints
+ val rewardEmitted = dataPoints.size * 2 // one extra token for each collected box as reward to collector
+ val epochOver = poolIn.creationInfo._1 < minStartHeight
+
+ val startData = 1L // we don't allow 0 data points
+ val startSum = 0L
+ // we expect data-points to be sorted in INCREASING order
+
+ val lastSortedSum = dataPoints.fold((startData, (true, startSum)), {
+ (t: (Long, (Boolean, Long)), b: Box) =>
+ val currData = b.R6[Long].get
+ val prevData = t._1
+ val wasSorted = t._2._1
+ val oldSum = t._2._2
+ val newSum = oldSum + currData // we don't have to worry about overflow, as it causes script to fail
+
+ val isSorted = wasSorted && prevData <= currData
+
+ (currData, (isSorted, newSum))
+ }
+ )
+
+ val lastData = lastSortedSum._1
+ val isSorted = lastSortedSum._2._1
+ val sum = lastSortedSum._2._2
+ val average = sum / dataPoints.size
+
+ val maxDelta = lastData * maxDeviationPercent / 100
+ val firstData = dataPoints(0).R6[Long].get
+
+ proveDlog(pubKey) &&
+ epochOver &&
+ enoughDataPoints &&
+ isSorted &&
+ lastData - firstData <= maxDelta &&
+ poolIn.tokens(0)._1 == poolNFT &&
+ poolOut.tokens(0) == poolIn.tokens(0) && // preserve pool NFT
+ poolOut.tokens(1)._1 == poolIn.tokens(1)._1 && // reward token id preserved
+ poolOut.tokens(1)._2 >= poolIn.tokens(1)._2 - rewardEmitted && // reward token amount correctly reduced
+ poolOut.tokens.size == poolIn.tokens.size && // cannot inject more tokens to pool box
+ poolOut.R4[Long].get == average && // rate
+ poolOut.R5[Int].get == poolIn.R5[Int].get + 1 && // counter
+ poolOut.propositionBytes == poolIn.propositionBytes && // preserve pool script
+ poolOut.value >= poolIn.value &&
+ poolOut.creationInfo._1 >= HEIGHT - buffer && // ensure that new box has correct start epoch height
+ selfOut.tokens == SELF.tokens && // refresh NFT preserved
+ selfOut.propositionBytes == SELF.propositionBytes && // script preserved
+ selfOut.value >= SELF.value
+}
diff --git a/eip-0023/contracts/update_contract.es b/eip-0023/contracts/update_contract.es
new file mode 100644
index 00000000..846e7ce8
--- /dev/null
+++ b/eip-0023/contracts/update_contract.es
@@ -0,0 +1,61 @@
+{ // This box (update box):
+ // Registers empty
+ //
+ // ballot boxes (Inputs)
+ // R4 the pub key of voter [GroupElement] (not used here)
+ // R5 the creation height of this box [Int]
+ // R6 the value voted for [Coll[Byte]] (hash of the new pool box script)
+ // R7 the reward token id in new box (optional, if not present then reward token is preserved)
+ // R8 the number of reward tokens in new box (optional, if not present then reward token is preserved))
+
+ val poolNFT = fromBase64("RytLYlBlU2hWbVlxM3Q2dzl6JEMmRilKQE1jUWZUalc=") // TODO replace with actual
+
+ val ballotTokenId = fromBase64("P0QoRy1LYVBkU2dWa1lwM3M2djl5JEImRSlIQE1iUWU=") // TODO replace with actual
+
+ val minVotes = 6 // TODO replace with actual
+
+ val poolIn = INPUTS(0) // pool box is 1st input
+ val poolOut = OUTPUTS(0) // copy of pool box is the 1st output
+
+ val updateBoxOut = OUTPUTS(1) // copy of this box is the 2nd output
+
+ // compute the hash of the pool output box. This should be the value voted for
+ val poolOutHash = blake2b256(poolOut.propositionBytes)
+ val rewardTokenId = poolOut.tokens(1)._1
+ val rewardAmt = poolOut.tokens(1)._2
+
+ val validPoolIn = poolIn.tokens(0)._1 == poolNFT
+
+ val validPoolOut = poolIn.tokens(0) == poolOut.tokens(0) && // NFT preserved
+ poolIn.value == poolOut.value && // value preserved
+ poolIn.R4[Long] == poolOut.R4[Long] && // rate preserved
+ poolIn.R5[Int] == poolOut.R5[Int] && // counter preserved
+ ! (poolOut.R6[Any].isDefined)
+
+
+ val validUpdateOut = updateBoxOut.tokens == SELF.tokens &&
+ updateBoxOut.propositionBytes == SELF.propositionBytes &&
+ updateBoxOut.value >= SELF.value &&
+ updateBoxOut.creationInfo._1 > SELF.creationInfo._1 &&
+ ! (updateBoxOut.R4[Any].isDefined)
+
+ val rewardTokenPreserved = poolIn.tokens(1)._1 == rewardTokenId && // check reward token id is preserved
+ poolIn.tokens(1)._2 == rewardAmt // check reward token amt is preserved
+
+ def isValidBallot(b:Box) = if (b.tokens.size > 0) {
+ val validRewardToken = if (b.R7[Coll[Byte]].isDefined && b.R8[Long].isDefined) {
+ b.R7[Coll[Byte]].get == rewardTokenId && // check rewardTokenId voted for
+ b.R8[Long].get == rewardAmt // check rewardTokenAmt voted for
+ } else rewardTokenPreserved
+ b.tokens(0)._1 == ballotTokenId &&
+ b.R5[Int].get == SELF.creationInfo._1 && // ensure vote corresponds to this box by checking creation height
+ b.R6[Coll[Byte]].get == poolOutHash && // check proposition voted for
+ validRewardToken
+ } else false
+
+ val ballotBoxes = INPUTS.filter(isValidBallot)
+
+ val votesCount = ballotBoxes.fold(0L, {(accum: Long, b: Box) => accum + b.tokens(0)._2})
+
+ sigmaProp(validPoolIn && validPoolOut && validUpdateOut && votesCount >= minVotes)
+}
diff --git a/eip-0023/eip-0023.md b/eip-0023/eip-0023.md
new file mode 100644
index 00000000..cfe36480
--- /dev/null
+++ b/eip-0023/eip-0023.md
@@ -0,0 +1,336 @@
+# Oracle Pool v2.0
+
+* Author: @scalahub, @greenhat, @kettlebell, @SethDusek
+* Status: Proposed
+* Created: 07-Sep-2021
+* License: CC0
+* Forking: not needed
+
+This is a proposed update to the oracle pool v1.0 currently deployed and documented in [EIP16](https://github.com/ergoplatform/eips/blob/eip16/eip-0016.md).
+
+## Contents
+- [Introduction](#introduction)
+ - [Prerequisites](#prerequisites)
+ - [Reward Mechanism](#reward-mechanism)
+ - [Refresh Mechanism](#refresh-mechanism)
+ - [Update Mechanism](#update-mechanism)
+- [Tokens](#tokens)
+- [Boxes](#boxes)
+- [Contracts](#contracts)
+ - [Pool Contract](#pool-contract)
+ - [Refresh Contract](#refresh-contract)
+ - [Oracle Contract](#oracle-contract)
+ - [Ballot Contract](#ballot-contract)
+ - [Update Contract](#update-contract)
+- [Trasactions](#transactions)
+ - [Refresh Pool](#refresh-pool)
+ - [Publish Data Point](#publish-data-point)
+ - [Extract Reward Tokens](#extract-reward-tokens)
+ - [Transfer Oracle Token](#transfer-oracle-token)
+ - [Vote for Update](#vote-for-update)
+ - [Update Pool Box](#update-pool-box)
+ - [Update Rules](#update-rules)
+ - [Transfer Ballot Token](#transfer-ballot-token)
+
+## Introduction
+In order to motivate the changes proposed in this document, consider the drawbacks of Oracle pool v1.0:
+
+1. Rewards generate a lot of dust
+2. Current rewards are too low (related to 1)
+3. There are two types of pool boxes. This makes dApps and update mechanism more complex
+4. Oracle tokens are non-transferable, and so oracles are locked permanently. The same goes with ballot tokens.
+
+Oracle pool v2.0 aims to address the above.
+
+Below is a summary of the main new features in v2.0 and how it differs from v1.0.
+
+- **Single pool address**: This version of the pool will have only one address, the *pool address*.
+- **Epoch counter**: Pool box will additionally store a counter that is incremented on each collection. This will allow more sophisticated dApps.
+- **Compact pool box**: Pool box is separated from the logic of pool management, which is captured instead in a **refresh** box. This makes the pool box very small for use in other dApps.
+- **Refresh box**: The refresh box is used for collecting data-points.
+- **Reward in tokens**: The posting reward will be in the form of tokens instead of Ergs. These tokens can be redeemed separately, which is not part of the protocol.
+ The pool box emits such reward tokens.
+- **No separate funding process**: The pool box emits only reward tokens and won't be handing out Ergs. Thus, there won't be a separate funding process required.
+- **Reward accumulated**: We will not be creating a new box for rewarding each posting to prevent dust. Instead, the rewards will be accumulated directly in the oracle boxes.
+- **Oracle boxes spent in collection**: Because the rewards must be accumulated, the oracle boxes will be considered as inputs rather than data-inputs when collecting individual rates for averaging.
+ These inputs will be spent, and a copy of the box with the reward will be created.
+ This gives us the ability to accumulate rewards, while keeping the transaction size similar to when using them as data-inputs in v1.0.
+ Additionally, this allows us to outsource part of the reward logic to the oracle boxes.
+
+ **Note:** The pool box will still be used as data input in other dApps.
+- **Transferable oracle tokens**: Oracle tokens are free to be transferred between public keys.
+- **Similar update mechanism**: We will have the similar update mechanism as in v1.0 (threshold number of ballot token holders must vote for an update).
+- **Transferable ballot tokens**: Similar to oracle tokens, the ballot tokens are free to be transferred between public keys.
+
+### Prerequisites
+
+The design below has some default parameters proposed for the ERG/USD oracle pool.
+
+If you want to run your own pool, for example, to serve a different pair, then the following parameters need to be decided.
+
+|Parameter|Symbol in code|Default value|Meaning|
+|---|---|---|---|
+|Epoch period|**epochLength**|30|The number of blocks for which a rate is locked in the pool box.
Each block is 2 mins on average
For a fast changing rate, use shorter value (such as 5)|
+|Buffer| **buffer** | 4 | The max error allowed in the declared start height of new a epoch
Ideally, this should be the height at which tx gets mined
However, due to congestion, we allow this error margin|
+|Total oracles| | 15 | The total number of oracles allowed to post data points
An oracle can post at most one data point at any time |
+|Minimum data points| **minDataPoints** | 4 | The minimum number of fresh data points needed to refresh pool box
A data point is fresh if it has been posted in the last **epochLength** blocks|
+|Maximum deviation percent| **maxDeviationPercent** | 5 | Maximum allowed difference between the first and last data points
in terms of the first data point
Data points are sorted in increasing order|
+|Total ballots| | 15 | The total number of people allowed to vote for an update |
+|Minimum votes| **minVotes** | 6 | The minimum votes needed to update pool params (any of the above)|
+
+Once the parameters are decided, generate the [tokens](#tokens) in the correct quantity and update the values in the [contracts](#contracts).
+Next, find the right number of trusted oracles and ballot holders and distribute the tokens to them.
+
+### Reward Mechanism
+
+In v1.0, the pool was responsible for rewarding each oracle for posting a data-point. In v2.0, the pool simply certifies that a data-point was posted, and a separate reward mechanism is proposed. This keeps the contract smaller and more flexible.
+
+The certificates are in the form of tokens emitted by the pool box. Thus, the pool box also contains the reward tokens to be emitted.
+
+Once there are sufficient number of such tokens, an oracle can exchange or burn them in return for a reward.
+
+### Refresh Mechanism
+
+In v1.0, the pool was refreshed by moving from the epoch-preparation phase to the live epoch phase.
+In v2.0, although there is a single pool box, the logic of pool refresh is stored in a separate box, the **refresh box**.
+The refresh box is identified by a separate token, the *refresh-NFT*, which is referenced in the pool and oracle contracts.
+
+### Update Mechanism
+
+The update mechanism is similar to that in v1.0. A threshold number (currently 6) of ballots must be cast for a proposed update. Such ballots are stored in a **ballot box** and identified by a **ballot token**.
+The ballot token holders and oracle token holders need not be the same people.
+The logic for updating the pool is stored in an **update box**, which identified by the **update-NFT**. This NFT is referenced in the pool and ballot boxes.
+The ballot box allows the owner to vote for 3 things:
+
+1. The script of the new pool box.
+2. The reward token id to be stored in the new pool box.
+3. The reward token amounts to be stored in the new pool box.
+
+Apart from this, the new pool box *must* preserve the remaining values from the old pool box.
+
+**Note** The update box logic acts on the pool box and not the refresh box.
+
+## Tokens
+
+The system has the following types of tokens. Note that we use the term **NFT** to refer to any token that was issued in quantity 1.
+
+| Token | Issued quantity | purpose | where stored |
+|---|---|---|---|
+|Pool-NFT | 1 | Identify pool box | Pool box |
+|Refresh-NFT | 1 | Identify refresh box | Refresh box |
+|Update-NFT | 1 | Identify update box | Update box |
+|Oracle tokens | 15 | Identify each oracle box | Oracle boxes |
+|Ballot tokens | 15 | Identify each ballot box | Ballot boxes |
+|Reward tokens | 100 million | Reward oracles | Pool box
Oracle boxes |
+
+## Boxes
+
+There are a total of 5 contracts, each corresponding to a box type
+
+| Box | Quantity | Tokens | Additional registers used | Purpose | Spending transactions |
+|---|---|---|---|---|---|
+|Pool | 1 | Pool-NFT
Reward tokens| R4: Rate (Long)
R5: Epoch counter (Int) | Publish pool rate for dApps
Emit reward tokens| Refresh pool
Update pool |
+|Refresh| 1 | Refresh-NFT | | Refresh pool box | Refresh pool |
+|Oracle | 15 | Oracle token
Reward tokens | R4: Public key (GroupElement)
R5: Epoch counter of pool box (Int)
R6: Published rate (Long) | Publish data-point
Accumulate reward tokens | Publish data-point,
Refresh pool,
Transfer oracle token,
Extract reward tokens|
+|Update | 1 | Update-NFT | | Updating pool box | Update pool box |
+|Ballot | 15 | Ballot token | R4: Public key (GroupElement)
R5: Update box creation height (Int)
R6: New pool box hash (Coll[Byte]) | Voting for updating pool box | Vote for update
Update pool box
Transfer ballot token |
+
+Before going into the transactions in the protocol, we first present the contracts for each of the above boxes.
+
+## Contracts with base-64-encoded hash of ergo-tree bytes
+
+- [Pool Contract](contracts/pool_contract.es) `8cJi+FGGU32jXyO8M2LeyWSWlerdcb1zxBWeZtyy7Y8=`
+- [Refresh Contract](contracts/refresh_contract.es) `cs5c5QEirstI4ZlTyrbTjlPwWYHRW+QsedtpyOSBnH4=`
+- [Oracle Contract](contracts/oracle_contract.es) `fhOYLO3s+NJCqTQDWUz0E+ffy2T1VG7ZnhSFs0RP948=`
+- [Ballot Contract](contracts/ballot_contract.es) `x01xAvK0CrRCwj36vp/jon7NARR1rxplSwI5B20ZNyI=`
+- [Update Contract](contracts/update_contract.es) `pQ7Dgjq1pUyISroP+RWEDf+kVNYAWjeFHzW+cpImhsQ=`
+Use this Scastie playground to calculate the above hashes - [https://scastie.scala-lang.org/hnTEm2lJQPG1wRYCqPz8LQ](https://scastie.scala-lang.org/hnTEm2lJQPG1wRYCqPz8LQ)
+## Transactions
+
+Oracle pool v2.0 has the following transactions.
+Each of the transactions below also contain the following boxes which will not be shown.
+
+1. Funding input box: this will be used to fund the transaction, and will be the last input.
+2. Fee output box: this will be the last output.
+3. Change output box: this is optional, and if present, will be the second-last output.
+
+| Transaction | Boxes Involved | Purpose |
+| --- | --- | --- |
+| Refresh pool | Pool
Refresh
Oracles | Refresh pool box |
+| Publish data point | Oracle | Publish data point |
+| Extract reward tokens | Oracle | Extract reward tokens to redeem via external mechanism |
+| Transfer oracle token | Oracle | Transfer oracle token to another public key|
+| Vote for update | Ballot | Vote for updating pool box |
+| Update pool | Pool
Update
Ballots | Update pool box |
+| Transfer ballot token | Ballot | Transfer ballot token to another public key|
+
+None of the transactions have data-inputs.
+
+### Refresh pool
+
+| Index | Input | Output |
+|---|---|---|
+| 0 | Pool | Pool |
+| 1 | Refresh | Refresh |
+| 2 | Oracle 1 | Oracle 1 |
+| 3 | Oracle 2 | Oracle 2 |
+| 4 | Oracle 3 | Oracle 3 |
+| ... | ... | ... |
+
+The purpose of this transaction is to take the average of the rates in all the oracle boxes and update the rate in the pool box whenever the
+epoch gets over (i.e., the current height is > creation height + epoch length).
+We consider such a pool box to be *stale* that needs to be refreshed.
+
+1. The first input is the pool box that simply requires the second input to be the refresh box (i.e., contain the refresh token) ([smart contract](#pool-contract)).
+2. The second input is a refresh box that contains the following logic ([smart contract](#refresh-contract)):
+ - The first input is a stale pool box, that is a box with the pool token and creation height lower than the current height minus epoch length.
+ - This transaction can only be done by someone holding a oracle token and having published an oracle box (see below).
+ - Any value published within the last epoch length by someone holding the oracle token is considered *latest*. This is called an oracle box.
+ In particular, for the box to be considered an oracle box, the following must hold:
+ - It must have an oracle token at index 0.
+ - Register R4 must contain a group element.
+ - Register R5 must be the current epoch counter (from R5 of the stale pool box).
+ - Register R6 must contain a long value, which will be assumed to be the rate.
+ - Its creation height must not be less than the current height minus epoch length.
+ - There must be at least a certain number of oracle boxes (currently 4) as inputs.
+ - The oracle boxes must be arranged in increasing order of their R6 values (rate).
+ - The first oracle box's rate must be within 5% of that of the last, and must be > 0.
+ - The first output must be a new pool box as follows:
+ - Registers R0 (value), R1 (script), R2 (tokens) are preserved from the old pool box.
+ - The creation height (stored in R3) must be at greater than or equal to the current height minus 4.
+ - The rate (stored in R4) must be the average of the rates in all the oracle boxes.
+ - The epoch counter (stored in R5) must be incremented by 1.
+ - Registers R6 and onward are empty.
+ - The quantity of the second token (reward) must be decremented by at most twice the number of valid rate boxes.
+ - The second output must be a new refresh box as follows:
+ - Registers R0 (value), R1 (script), and the first token (refresh NFT) are preserved from the old refresh box.
+3. Each input oracle box has following logic ([smart contract](#oracle-contract)):
+ - The first input is a pool box (i.e., has the pool token).
+ - An output oracle box (acting as a copy of this box) must be created as follows:
+ - The following values are directly copied: R0 (nanoErgs), R1 (script), the first token (oracle token) (stored in R2) and R4 (GroupElement).
+ - The second token (reward token) is incremented by at least 1.
+
+Suppose there are *n* valid oracle boxes, then there are 2*n* reward tokens released.
+It is expected that whoever creates the refresh transaction (the *collector*) takes *n*+1 reward tokens, and the other oracles get 1 token each.
+This gives incentive to use as many oracle boxes as possible during the refresh.
+
+### Publish data-point
+
+| Index | Input | Output |
+|---|---|---|
+| 0 | Oracle | Oracle |
+
+This allows an oracle to publish data-point for collection in next epoch.
+This entails spending the oracle box and creating a new oracle box as per the [smart contract](#oracle-contract).
+
+1. The public key stored in R4 defines who can spend the oracle box.
+2. The logic requires the new oracle box to be as follows:
+ - The script and the first token are copied from this box.
+ - R4 contains a group element.
+ - The second token is the reward token in some non-zero quantity.
+3. The following rules (not enforced by the smart contract) to be followed.
+ - It should store the same group element in R4.
+ - It should store epoch counter of the current pool box in R5.
+ - It should store the rate to publish in R6.
+ - It should ensure reward tokens are preserved.
+
+### Extract reward tokens
+
+| Index | Input | Output |
+|---|---|---|
+| 0 | Oracle | Oracle |
+| 1 | | Box with freed reward tokens |
+
+This allows an oracle to extract tokens obtained from publishing data point to redeem them elsewhere.
+
+The transaction is similar to the [publish data-point](#publish-data-point) transaction, except that Step 3 is modified as follows.
+
+3. The following rules (not enforced by the smart contract) to be followed.
+ - It should store the same group element in R4.
+ - It should keep at least 1 reward token.
+ - It should store the balance reward tokens in some other box.
+
+
+### Transfer oracle token
+
+| Index | Input | Output |
+|---|---|---|
+| 0 | Oracle | Oracle |
+
+This is used to transfer the ownership of an oracle token to another public key.
+
+The transaction is similar to the [publish data-point](#publish-data-point) transaction, except that Step 3 is modified as follows.
+
+3. The following rules (not enforced by the smart contract) to be followed.
+ - It should store the new owner's group element in R4.
+ - It should check that there is only 1 reward token is in the oracle box and send it to the new owner along with oracle token.
+
+### Vote for update
+
+| Index | Input | Output |
+|---|---|---|
+| 0 | Ballot | Ballot |
+
+This is used by ballot token holders to vote for updating the pool box address.
+
+The input and output are ballot boxes such that the following holds as per the [smart contract](#ballot-contract):
+- There is a group element in R4.
+
+The following (not enforced by the contract) must be done for a proper vote:
+- R5 of type `Int` contains the creation height of the current update box.
+- R6 of type `Coll[Byte]` contains the hash of the address of the new pool box.
+- R7 of type `Coll[Byte]` contains the reward token id for the new pool box.
+- R8 of type `Int` contains the reward token amount for the new pool box.
+
+### Update Pool box
+
+| Index | Input | Output |
+|---|---|---|
+| 0 | Pool | New Pool |
+| 1 | Update | Update |
+| 2 | Ballot 1 | Ballot 1 |
+| 3 | Ballot 2 | Ballot 2 |
+| 4 | Ballot 3 | Ballot 3 |
+| ... | ... | ... |
+
+This updates the pool box (i.e., transfers the poolNFT to a new address).
+
+The ballots used in inputs must satisfy the following as per the [smart contract](#ballot-contract):
+- There must be an output ballot box with the same public key in R4 and rest registers empty.
+
+The update box additionally ensures following as per the [smart contract](#update-contract):
+- Each ballot box has R5 containing the hash of the new pool box script (address).
+- Each ballot box has R6 containing this box's creation height.
+- Each ballot box has R7 containing the new pool box's reward token id.
+- Each ballot box has R8 containing the new pool box's reward token amount.
+- The remaining registers (apart from creation height) and tokens in the old pool box are preserved in the new pool box.
+- There is a copy of the update box in outputs with everything preserved but with larger creation height.
+- There are at least a threshold number of votes.
+
+We store the update box's creation height in the ballot's R5 to ensure that a vote is actually for the current update box.
+
+#### Update Rules
+
+In order to keep the [pool contract](#pool-contract) as compact as possible, many parameters are hard-wired in the [refresh contract](#refresh-contract).
+Depending on what is to be updated, we may need to update other items as well. In particular, the following rules apply:
+
+| To update | Also must update | Can preserve |
+|---|---|---|
+|Pool params
(like epoch length) | Refresh-NFT
Refresh box | Reward tokens
Update-NFT
Update box
Ballot tokens
Ballot boxes
Oracle tokens
Oracle boxes |
+|Refresh-NFT | Refresh box | Reward tokens
Update-NFT
Update box
Ballot tokens
Ballot boxes
Oracle tokens
Oracle boxes |
+|Update-NFT | Update box
Ballot tokens
Ballot boxes | Refresh-NFT
Refresh box
Reward tokens
Oracle tokens
Oracle boxes |
+|Oracle tokens | Refresh-NFT
Refresh box
Oracle boxes
| Reward tokens
Update-NFT
Update box
Ballot tokens
Ballot boxes |
+|Reward tokens | Pool box| Refresh-NFT
Refresh box
Update-NFT
Update box
Ballot tokens
Ballot boxes
Oracle tokens
Oracle boxes |
+|Ballot tokens | Update-NFT
Update box
Ballot boxes | Refresh-NFT
Refresh box
Reward tokens
Oracle tokens
Oracle boxes |
+
+After an update, the older versions of the boxes will be left for garbage collection via storage-rent, and their tokens will become free for miners to take.
+Hence, it is beneficial to update all the related tokens anyway.
+
+
+### Transfer ballot token
+
+| Index | Input | Output |
+|---|---|---|
+| 0 | Ballot | Ballot |
+
+This is similar to the [transfer oracle token](#transfer-oracle-token) and [vote for update](#vote-for-update) transactions, except that it is used to transfer ownership of a ballot token to another public key.