Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ jobs:
- run: pnpm install

- name: Lint
run: |
pnpm format:check
run: pnpm lint

- name: Test
run: pnpm test

- name: Build documentation
run: |
pnpm docs:build
run: pnpm docs:build

- name: Serve docs and check links
run: |
Expand Down
2 changes: 1 addition & 1 deletion docs/dapps/integration/security-considerations.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Similarly, our OEV implementation uses this mechanism, ensuring OEV updates cont
OEV updates provide identical guarantees to regular updates—they are on-chain aggregations of API provider-signed data—so they introduce no additional [data correctness](#data-correctness) risk.
The OEV auction mechanism allows winners to frontrun updates of an artificially delayed base feed, a tradeoff designed to benefit the dApp.

Here's how the process works.
Here's how the process works for auctions with a length of 30 seconds.
The lifecycle of a data point consists of three phases:

1. From 0-30 seconds: OEV searchers examine the data point and place bids on potential OEV opportunities.
Expand Down
77 changes: 67 additions & 10 deletions docs/oev-searchers/in-depth/oev-auctioneer.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,27 +42,85 @@ understand and comply with in order to successfully participate in auctions.

| Name | Value | Description |
| ------------------------------------- | ----- | -------------------------------------------------------------------------------------------------- |
| AUCTION_LENGTH_SECONDS | 30 | How long an auction lasts. |
| OEV_AUCTIONS_MAJOR_VERSION | 1 | Increased when we release any breaking change relevant to OEV auctions. |
| COLLATERAL_REQUIREMENT_BUFFER_PERCENT | 5 | The additional percentage of the bidder's collateral to mitigate against price changes. |
| BID_PHASE_LENGTH_SECONDS | 25 | The length of the bid phase during which searchers can place their bids. |
| AWARD_PHASE_LENGTH_SECONDS | 5 | The length of the award phase during which Auctioneer attempts to resolve the auction. |
| REPORT_FULFILLMENT_PERIOD_SECONDS | 86400 | The fulfillment period, during which the auction winner is able to report payment for the OEV bid. |
| MINIMUM_BID_EXPIRING_SECONDS | 15 | The minimum expiring time for a bid to be considered eligible for award. |
| PLACED_BIDS_BLOCK_RANGE | 300 | The number of blocks queried for placed bids during award phase. |

### Auction length

The auction length is chain dependent.

<!-- BEGIN:chain-auction-length-table -->

| Chain | Length (seconds) |
| --------------- | ---------------- |
| ApeChain | 30 |
| Arbitrum One | 15 |
| Avalanche | 30 |
| Base | 30 |
| Berachain | 30 |
| Bitlayer | 30 |
| Blast | 30 |
| BNB Smart Chain | 30 |
| BOB | 30 |
| Core | 30 |
| Ethereum | 30 |
| Fraxtal | 30 |
| Gnosis Chain | 30 |
| Ink | 30 |
| Katana | 30 |
| Kava | 30 |
| Linea | 30 |
| Lumia | 30 |
| Manta | 30 |
| Mantle | 30 |
| Merlin | 30 |
| Metal L2 | 30 |
| Metis | 30 |
| Mode | 30 |
| Moonbeam | 30 |
| Moonriver | 30 |
| opBNB | 30 |
| Optimism | 30 |
| Polygon | 30 |
| Polygon zkEVM | 30 |
| Ronin | 30 |
| Scroll | 30 |
| Sei | 30 |
| Soneium | 30 |
| Sonic | 30 |
| Taiko | 30 |
| Unichain | 30 |
| World Chain | 30 |
| X Layer | 30 |
| Zircuit | 30 |

<!-- END:chain-auction-length-table -->

### Bid phase length

The length of the bid phase during which searchers can place their bids. The bid phase length is auction length dependent.

```js
const bidPhaseLength = auctionLength - AWARD_PHASE_LENGTH_SECONDS;
```

### Auction offset

Auctions repeat indefinitely and take a fixed amount of time. The first auction
starts at the UNIX timestamp 0 (midnight UTC on 1st of January 1970) plus an
offset based on the dApp ID.

```solidity
uint256(keccak256(abi.encodePacked(uint256(dAppId)))) % AUCTION_LENGTH_SECONDS;
uint256(keccak256(abi.encodePacked(uint256(dappId)))) % auctionLength;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Yeah, the casing was incorrect.

```

::: info ℹ️ Example

Say there is a dApp with ID `13` and `AUCTION_LENGTH_SECONDS=30`
Say there is a dApp with ID `13` and `auctionLength=30`

- When we encode and hash the dApp ID, we get
`0xd7b6990105719101dabeb77144f2a3385c8033acd3af97e9423a695e81ad1eb5`.
Expand Down Expand Up @@ -97,9 +155,7 @@ Let's break down the components of the bid topic:
protocol specs, is denoted by this major version being incremented. Refer to
the current value of `OEV_AUCTIONS_MAJOR_VERSION` constant.
2. `dappId` - The dApp ID for which the auction is being held.
3. `auctionLength` - The length of the auction. This parameter must be set to
`AUCTION_LENGTH_SECONDS`. It is one of the most important parameters, so
we're explicitly including it in the bid topic to highlight its importance.
3. `auctionLength` - The length of the auction in seconds. It is one of the most important parameters, so we're explicitly including it in the bid topic to highlight its importance.
4. `signedDataTimestampCutoff` - The cutoff timestamp of the signed data. The auction winner is permitted to only use signed data with timestamps smaller than or equal to this. It is equal to the end of the bid phase of the
auction.

Expand All @@ -108,10 +164,10 @@ Let's break down the components of the bid topic:
Auctions repeat continuously and indefinitely. To calculate the
`signedDataTimestampCutoff` that is to be specified in the bid topic, one needs
to calculate the `startTimestamp` of the next auction. This depends on the auction
offset, `BID_PHASE_LENGTH_SECONDS` and the current time.
offset, `bidPhaseLength` and the current time.

For example, dApp with ID `13` has an auction offset of `17`. With
`AUCTION_LENGTH_SECONDS=30` and `BID_PHASE_LENGTH_SECONDS=25` this gives the
`auctionLength=30` and `bidPhaseLength=25` this gives the
following sequence of auctions:

| `startTimestamp` | `signedDataTimestampCutoff` | End of award phase |
Expand Down Expand Up @@ -205,7 +261,7 @@ transaction.
Each auction is split into two phases:

1. Bid phase - During this phase, searchers are free to submit their bids.
This phase takes `BID_PHASE_LENGTH_SECONDS`.
This phase takes `bidPhaseLength`.
2. Award phase - During this phase, Auctioneer determines and awards the winner.
Bids placed during this period are ignored.

Expand All @@ -221,6 +277,7 @@ as soon as possible. The following happens under the hood:
selected
6. Prepare and submit the award for the auction winner on the OEV Network

Auctioneer may take longer than the allotted `AWARD_PHASE_LENGTH_SECONDS` to award the winner.
Under rare circumstances, when Auctioneer is unable to fetch the block or the
logs from the OEV Network, the auction will be aborted and no winner is chosen.
Similarly, if the auction award transaction fails, there will be no retry,
Expand Down
43 changes: 43 additions & 0 deletions docs/oev-searchers/in-depth/oev-auctioneer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import path from 'node:path';
import fs from 'node:fs';
import { test, expect } from 'vitest';
import { CHAINS } from '@api3/contracts';
import { getChains } from '@api3/dapi-management';

const supportedMainnets = getChains()
.filter((chain) => chain.stage === 'active')
.map((chain) => CHAINS.find((api3Chain) => api3Chain.id === chain.id)!)
.filter((chain) => !chain.testnet)
.filter((chain) => chain.alias !== 'oev-network')
.map((chain) => chain.name)
.sort((a, b) => a.localeCompare(b));

test('the auction length section lists all the supported chains', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Yeah, I imagined something like this

const docPath = path.join(
process.cwd(),
'docs',
'oev-searchers',
'in-depth',
'oev-auctioneer.md'
);
const content = fs.readFileSync(docPath, 'utf8');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make sure this errors out nicely in case the path doesn't exist (when we move the files arouind)


const chains = extractContentBetweenMarkers(content, 'chain-auction-length-table')
.split('\n')
.filter(Boolean) // Get rid of blank lines
.slice(2) // Get rid of the header rows
.map((row) => row.split('|')[1].trim());

expect(chains).toStrictEqual(supportedMainnets);
});

function extractContentBetweenMarkers(content: string, key: string) {
const beginMarker = `<!-- BEGIN:${key} -->`;
const endMarker = `<!-- END:${key} -->`;
const beginIndex = content.indexOf(beginMarker);
const endIndex = content.indexOf(endMarker);
if (beginIndex === -1 || endIndex === -1) {
throw new Error(`Could not find markers "${beginMarker}" and "${endMarker}"`);
}
return content.slice(beginIndex + beginMarker.length, endIndex).trim();
}
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@
"docs:serve": "vitepress serve docs --port 8082",
"format": "prettier --write --cache \"./**/*.{js,vue,md,json,yaml}\" --log-level silent",
"format:check": "prettier --check --cache \"./**/*.{js,vue,md,json,yaml}\"",
"lint": "pnpm format:check && pnpm lint:tsc",
"lint:tsc": "pnpm tsc",
"test": "vitest run",
"generate-llms-files": "node scripts/generate-llms-files.js",
"prepare": "husky",
"firebase:emulator": "pnpm docs:build; firebase emulators:start"
},
"devDependencies": {
"@api3/contracts": "^27.0.0",
"@api3/dapi-management": "^3.48.0",
"@types/node": "^22.18.0",
"axios": "^1.11.0",
"colors": "^1.4.0",
"file": "^0.2.2",
Expand All @@ -24,7 +30,9 @@
"medium-zoom": "^1.1.0",
"oust": "^2.0.4",
"prettier": "^3.6.2",
"typescript": "^5.9.2",
"vitepress": "1.6.4",
"vitest": "^3.2.4",
"walk-sync": "^3.0.0"
}
}
Loading