-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Auction scripts #345
base: main
Are you sure you want to change the base?
Auction scripts #345
Changes from 6 commits
59d7f79
ec1459e
ccfb188
f8b2711
299027f
d56c3aa
c0f11da
9eb48cd
46a1bc6
a880641
0c24922
6f068a3
e88e3bf
1d10eb1
8387816
93bafb6
34f6567
040279a
2896777
b387c4f
5bc0e19
acd5bd5
a7028d1
574397c
030ce1c
c141d86
aa7f584
2472c2c
390507d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import fs from "fs"; | ||
import path from "path"; | ||
import { fileURLToPath } from "url"; | ||
import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; | ||
|
||
// ESM-friendly __dirname | ||
const __filename = fileURLToPath(import.meta.url); | ||
const __dirname = path.dirname(__filename); | ||
|
||
// Relative path to your allocations file. | ||
// Adjust if needed, or make it an absolute path. | ||
const ALLOCATIONS_FILE = path.join(__dirname, "../../script/data/auction/allocations.txt"); | ||
|
||
// JSON file to dump the entire Merkle tree. | ||
const OUTPUT_JSON = path.join(__dirname, "../../script/data/auction/merkle-tree.json"); | ||
|
||
async function main() { | ||
// 1) Read lines from allocations.txt | ||
const content = fs.readFileSync(ALLOCATIONS_FILE, "utf-8"); | ||
|
||
// Split into non-empty lines | ||
const lines = content.split(/\r?\n/).filter(line => line.trim().length > 0); | ||
|
||
// 2) Build the "values" array for Merkle tree | ||
// Each element is [ userAddress, saleTokenAllocation, usdcAllocation ] | ||
// all as strings, matching ["address","uint256","uint256"] in the tree schema. | ||
const values = []; | ||
for (const line of lines) { | ||
const [address, saleAlloc, usdcAlloc] = line.trim().split(/\s+/); | ||
if (!address || !saleAlloc || !usdcAlloc) { | ||
console.warn(`Skipping malformed line: "${line}"`); | ||
continue; | ||
} | ||
|
||
// Push as an array: [ string, string, string ] | ||
values.push([address, saleAlloc, usdcAlloc]); | ||
} | ||
|
||
if (values.length === 0) { | ||
console.error("No allocations found. Exiting."); | ||
process.exit(1); | ||
} | ||
|
||
// 3) Create the Merkle tree | ||
// The schema must match the types: ["address", "uint256", "uint256"] | ||
const tree = StandardMerkleTree.of(values, ["address", "uint256", "uint256"]); | ||
|
||
// 4) Print the Merkle root (paste this into your contract) | ||
console.log("Merkle Root:", tree.root); | ||
|
||
// 5) Write the entire tree to a JSON file (for off-chain reference) | ||
fs.writeFileSync(OUTPUT_JSON, JSON.stringify(tree.dump()), "utf-8"); | ||
console.log(`Merkle tree dumped to ${OUTPUT_JSON}`); | ||
} | ||
|
||
main().catch(err => { | ||
console.error(err); | ||
process.exit(1); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
|
||
const USDC_SCALE = 1_000_000n; // 1 USDC = 1e6 | ||
|
||
/** | ||
* Runs a uniform-price auction using BigInt with: | ||
* - USDC in 6 decimals (1 USDC = 1e6) | ||
* - Tokens in 18 decimals (1 TOKEN = 1e18) | ||
* | ||
* @param {Object[]} bids | ||
* @param {string} bids[].address - Ethereum address. | ||
* @param {bigint} bids[].usdcAmount - USDC in 1e6 units. | ||
* @param {bigint} bids[].maxPrice - Max price in USDC’s 1e6 format. | ||
* @param {bigint} bids[].priority - Priority for tie-breaks. | ||
* @param {bigint} totalTokens - Total tokens to sell in 1e18 units. | ||
* | ||
* @returns {{ | ||
* finalPrice: bigint, // Price per token in 1e6 USDC units (6 decimals) | ||
* allocations: { | ||
* [address: string]: { | ||
* tokens: bigint, // Tokens allocated (18 decimals) | ||
* usedUSDC: bigint, // USDC used (6 decimals) | ||
* refundedUSDC: bigint // USDC refunded (6 decimals) | ||
* } | ||
* } | ||
* }} | ||
*/ | ||
function runAuction(bids, totalTokens) { | ||
// Constants for scaling | ||
const TOKEN_SCALE = 1_000_000_000_000_000_000n; // 1 TOKEN = 1e18 | ||
|
||
// 1) Sort bids by maxPrice (desc), then priority (desc) | ||
bids.sort((a, b) => { | ||
if (b.maxPrice === a.maxPrice) { | ||
return Number(b.priority - a.priority); | ||
} | ||
return Number(b.maxPrice - a.maxPrice); | ||
}); | ||
|
||
// 2) Determine the clearing price | ||
let tokensAccumulated = 0n; | ||
let finalPrice = 0n; | ||
|
||
for (const bid of bids) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure if this logic is correct. You are assuming people are getting the tokens at their bid price which is incorrect. For example I bid 30, you bid 30 and the third person bids 10. I should be getting my tokens at 10 not at 30, so to calculate tokens accumulated you need to use 10 not 30 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. otherwise we are not grabbing the real last bid, we are finishing up earlier than we should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't assume people are getting the tokens at their bid. The first look is only to calculate how many tokens people are willing to buy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but youa re using this to calculate the finalPrice |
||
// tokensDemanded = (usdcAmount * TOKEN_SCALE) / maxPrice | ||
const tokensDemanded = (bid.usdcAmount * TOKEN_SCALE) / bid.maxPrice; | ||
|
||
tokensAccumulated += tokensDemanded; | ||
if (tokensAccumulated >= totalTokens) { | ||
finalPrice = bid.maxPrice; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is what I think is incorrect There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is like the worst case. Because if I bid $300 for $30, you are only accumulating 10 tokens but in reality maybe you should have accumulated 30 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so you would run out of tokens earlier There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's why I think the loop needs to be over each bid price first, and then over bids. It needs to be a double loop There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah. I agree. thanks. |
||
break; | ||
} | ||
} | ||
|
||
// If finalPrice is 0, not enough demand to sell all tokens => "not successful" | ||
if (finalPrice === 0n) { | ||
const allocations = {}; | ||
for (const bid of bids) { | ||
allocations[bid.address] = { | ||
tokens: 0n, | ||
usedUSDC: 0n, | ||
refundedUSDC: bid.usdcAmount | ||
}; | ||
} | ||
return { finalPrice, allocations }; | ||
} | ||
|
||
// 3) Allocate tokens | ||
let tokensLeft = totalTokens; | ||
const allocations = {}; | ||
|
||
for (const bid of bids) { | ||
// If user bid below finalPrice => no tokens | ||
if (bid.maxPrice < finalPrice) { | ||
allocations[bid.address] = { | ||
tokens: 0n, | ||
usedUSDC: 0n, | ||
refundedUSDC: bid.usdcAmount | ||
}; | ||
continue; | ||
} | ||
|
||
// tokensWanted = (usdcAmount * TOKEN_SCALE) / finalPrice | ||
let tokensWanted = (bid.usdcAmount * TOKEN_SCALE) / finalPrice; | ||
if (tokensWanted > tokensLeft) { | ||
tokensWanted = tokensLeft; | ||
} | ||
|
||
// usedUSDC = (tokensAllocated * finalPrice) / TOKEN_SCALE | ||
let usedUSDC = (tokensWanted * finalPrice) / TOKEN_SCALE; | ||
|
||
// Refund leftover USDC. Clamp to 0 if negative or 1. | ||
let refundedUSDC = bid.usdcAmount - usedUSDC; | ||
if (refundedUSDC < 2n) { | ||
refundedUSDC = 0n; | ||
} | ||
|
||
allocations[bid.address] = { | ||
tokens: tokensWanted, | ||
usedUSDC, | ||
refundedUSDC | ||
}; | ||
|
||
tokensLeft -= tokensWanted; | ||
if (tokensLeft <= 0n) { | ||
break; | ||
} | ||
} | ||
|
||
// If the loop broke early, ensure leftover bidders get 0 tokens | ||
if (tokensLeft > 0n) { | ||
for (const bid of bids) { | ||
if (!allocations[bid.address]) { | ||
allocations[bid.address] = { | ||
tokens: 0n, | ||
usedUSDC: 0n, | ||
refundedUSDC: bid.usdcAmount | ||
}; | ||
} | ||
} | ||
} | ||
|
||
return { finalPrice, allocations }; | ||
} | ||
|
||
// ---------------------------------------------------------------------- | ||
// Read input file (address amount maxPrice priority) => BigInt | ||
// ---------------------------------------------------------------------- | ||
function readBidsFromFile(filePath) { | ||
const content = fs.readFileSync(filePath, 'utf-8'); | ||
const lines = content.split(/\r?\n/); | ||
|
||
const bids = []; | ||
for (const line of lines) { | ||
if (!line.trim()) continue; // skip empty lines | ||
|
||
const [address, amountStr, maxPriceStr, priorityStr] = line.trim().split(/\s+/); | ||
if (!address || !amountStr || !maxPriceStr || !priorityStr) { | ||
continue; // skip malformed lines | ||
} | ||
|
||
// Convert to BigInt (these are already in 1e6 for USDC or just integer priority) | ||
const usdcAmount = BigInt(amountStr); | ||
const maxPrice = BigInt(maxPriceStr); | ||
const priority = BigInt(priorityStr); | ||
|
||
bids.push({ address, usdcAmount, maxPrice, priority }); | ||
} | ||
return bids; | ||
} | ||
|
||
// ---------------------------------------------------------------------- | ||
// Write output file | ||
// ---------------------------------------------------------------------- | ||
function writeOutputToFile(filePath, finalPrice, allocations) { | ||
let output = ''; | ||
for (const address in allocations) { | ||
const { tokens, usedUSDC, refundedUSDC } = allocations[address]; | ||
output += `${address} ${tokens.toString()} ${usedUSDC.toString()} ${refundedUSDC.toString()}\n`; | ||
} | ||
|
||
fs.writeFileSync(filePath, output, 'utf-8'); | ||
} | ||
|
||
// ---------------------------------------------------------------------- | ||
// Main | ||
// ---------------------------------------------------------------------- | ||
function main() { | ||
const inputFilePath = path.join(__dirname, '../../script/data/auction/bids.txt'); | ||
const outputFilePath = path.join(__dirname, '../../script/data/auction/allocations.txt'); | ||
|
||
const bids = readBidsFromFile(inputFilePath); | ||
|
||
// If we want to sell 1,000 tokens, in 18-decimal: | ||
// 1000 tokens * 1e18 = 1e21 | ||
const totalTokens = 40_000n * 1_000_000_000_000_000_000n; // 1000 * 1e18 = 1e21 | ||
|
||
const { finalPrice, allocations } = runAuction(bids, totalTokens); | ||
|
||
writeOutputToFile(outputFilePath, finalPrice, allocations); | ||
|
||
console.log(`Auction complete. Final Price: $${(finalPrice/USDC_SCALE).toString()}.00. Results written to ${outputFilePath}`); | ||
} | ||
|
||
// Execute if called directly (e.g. `node auction-bigint.js`) | ||
if (require.main === module) { | ||
main(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
#!/bin/bash | ||
|
||
# Ensure KINTO_RPC_URL is set | ||
if [ -z "$KINTO_RPC_URL" ]; then | ||
echo "Error: KINTO_RPC_URL is not set." | ||
exit 1 | ||
fi | ||
|
||
# Output file | ||
output="./script/data/auction/bids.txt" | ||
rm -f "$output" # Remove previous output file if it exists | ||
touch "$output" | ||
|
||
# Maximum number of parallel jobs | ||
max_jobs=8 | ||
|
||
# Function to process a single user | ||
process_user() { | ||
local user=$1 | ||
local deposit | ||
local maxPrice | ||
|
||
deposit=$(cast call --rpc-url "$KINTO_RPC_URL" \ | ||
0x5a1E00884e35bF2dC39Af51712D08bEF24b1817f \ | ||
"deposits(address)" "$user" | cast to-dec) | ||
|
||
maxPrice=$(cast call --rpc-url "$KINTO_RPC_URL" \ | ||
0x5a1E00884e35bF2dC39Af51712D08bEF24b1817f \ | ||
"maxPrices(address)" "$user" | cast to-dec) | ||
|
||
# Append the result to the output file. | ||
# Using a newline ensures each record is separate. | ||
echo "$user $deposit $maxPrice 0" >> "$output" | ||
} | ||
|
||
# Read users from file and process in parallel | ||
while IFS= read -r user || [ -n "$user" ]; do | ||
# Launch the process in the background | ||
process_user "$user" & | ||
|
||
# Limit the number of parallel jobs | ||
while [ "$(jobs -r | wc -l)" -ge "$max_jobs" ]; do | ||
sleep 0.1 | ||
done | ||
done < ./script/data/auction/users.txt | ||
|
||
# Wait for any remaining background processes to finish | ||
wait | ||
|
||
echo "Data fetched and stored in $output" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
#!/bin/bash | ||
set -euo pipefail | ||
|
||
# Ensure KINTO_RPC_URL is set | ||
if [ -z "${KINTO_RPC_URL:-}" ]; then | ||
echo "Error: KINTO_RPC_URL is not set." | ||
exit 1 | ||
fi | ||
|
||
# Fetch logs and process them: | ||
# 1. `cast logs` fetches the logs for the Deposited event. | ||
# 2. `awk` selects the appropriate lines (skip two lines after matching "topics: ["). | ||
# 3. `xargs` runs `cast parse-bytes32-address` for each address. | ||
# 4. `sort -u` ensures only unique addresses are written to the file. | ||
cast logs --rpc-url "$KINTO_RPC_URL" --from-block 731179 --to-block latest \ | ||
'Deposited(address indexed user, uint256 amount)' \ | ||
--address 0x5a1E00884e35bF2dC39Af51712D08bEF24b1817f | \ | ||
awk '/topics: \[/{getline; getline; print}' | \ | ||
xargs -n1 cast parse-bytes32-address | sort -u > ./script/data/auction/users.txt | ||
|
||
echo "Unique user addresses saved to 'users'" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
./utils/auction/fetch-users.sh | ||
|
||
./utils/auction/fetch-bids.sh | ||
|
||
node utils/auction/clear-auction.js | ||
|
||
node utils/auction/build-merkle-proof.mjs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how are you calculating priority? Take into account that within engen, the engen balance matters and then emissaries and then people sorted by their K balance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, calculating priority would be another script, as we have to fetch data for Engen and Balances.