The Resolution

After the pool is closed, the liquidity ladder allocations will be decided through a fair algorithm

Non-technical explanation of the algorithm

The smart contract generates a seed.

Using that seed, the algorithm will select participants in a random order and will allocate their funds on each level. It will start from the Level 1 to the Level 10 (most profitable to less profitable)

If a user allocation is higher than the rest of the space on a given level, his allocation will be distributed in the next most profitable level.

Technical explanation of the algorithm

We have created a script to replicate the logic of the ladder results.

Everyone can execute it independently to check that the election has been done following a fair algorithm.

import BigNumber from "bignumber.js";

// Configure BigNumber for high precision calculations
BigNumber.config({
  DECIMAL_PLACES: 10,
  ROUNDING_MODE: BigNumber.ROUND_HALF_UP,
});

// Precision-free allocation system using BigNumber for exact calculations
// Level capacities are rounded to 0.1 SOL increments

// Ladder constants (in SOL)
const LADDER_REFERENCE_CAP_SOL = new BigNumber(2000);
const LEVELS_SLOTS_SOL = {
  L1: new BigNumber(100),
  L2: new BigNumber(138),
  L3: new BigNumber(147),
  L4: new BigNumber(424),
  L5: new BigNumber(396),
  L6: new BigNumber(342),
  L7: new BigNumber(246),
  L8: new BigNumber(90),
  L9: new BigNumber(63),
  L10: new BigNumber(54),
};

// Round to 0.1 SOL precision (only for level capacities)
function roundTo01SOL(amount: BigNumber): BigNumber {
  return amount.decimalPlaces(1, BigNumber.ROUND_HALF_UP);
}

// Generate the random number from the seed
function createLCG(seed: number) {
  let state = seed >>> 0;
  const a = 1664525;
  const c = 1013904223;
  const m = 2 ** 32;

  return function rand(): number {
    state = (a * state + c) % m;
    return state / m; // [0, 1)
  };
}

function generateParticipantAllocations(
  userWallets: string[],
  userParticipations: Record<string, BigNumber>, // in SOL
  seed: number,
  totalLiquidity: BigNumber // in SOL
): Record<
  string, // Level
  Record<string, BigNumber> // User -> Amount in SOL
> {
  let currentLevel = 1;
  const allocations: Record<string, Record<string, BigNumber>> = {};

  // Initialize all 10 levels
  for (let i = 1; i <= 10; i++) {
    allocations[i] = {};
  }

  const unpickedUsers = [...userWallets];

  // Create a copy of participations to modify
  const remainingParticipations: Record<string, BigNumber> = {};
  for (const [user, amount] of Object.entries(userParticipations)) {
    remainingParticipations[user] = amount;
  }

  // Pick a random user wallet until we have picked all of them
  const rand = createLCG(seed);

  // Calculate overcap multiplier
  const overcapMultiplier = totalLiquidity.dividedBy(LADDER_REFERENCE_CAP_SOL);

  // Pre-calculate level capacities (only round these, not user participations)
  const levelCapacities: Record<number, BigNumber> = {};
  for (let i = 1; i <= 10; i++) {
    const baseCapacity =
      LEVELS_SLOTS_SOL[`L${i}` as keyof typeof LEVELS_SLOTS_SOL];
    levelCapacities[i] = roundTo01SOL(
      baseCapacity.multipliedBy(overcapMultiplier)
    );
  }

  let currentLevelCapacity = levelCapacities[currentLevel];

  while (unpickedUsers.length > 0 && currentLevel <= 10) {
    const idx = Math.floor(rand() * unpickedUsers.length);
    const user = unpickedUsers.splice(idx, 1)[0];
    const participation = remainingParticipations[user!];

    if (participation.isGreaterThan(currentLevelCapacity)) {
      // User's participation exceeds current level capacity
      if (currentLevelCapacity.isGreaterThan(0)) {
        allocations[currentLevel][user!] = currentLevelCapacity;
        // Put the user back with remaining participation
        const remainingParticipation =
          participation.minus(currentLevelCapacity);
        remainingParticipations[user!] = remainingParticipation;
        unpickedUsers.push(user!);
        currentLevelCapacity = new BigNumber(0);
      } else {
        // No capacity left, put user back
        unpickedUsers.push(user!);
      }

      // Move to next level
      currentLevel++;
      if (currentLevel <= 10) {
        currentLevelCapacity = levelCapacities[currentLevel];
      }
    } else {
      // User's participation fits in current level
      allocations[currentLevel][user!] = participation;
      currentLevelCapacity = currentLevelCapacity.minus(participation);

      // Check if level is full and move to next level
      if (currentLevelCapacity.isLessThanOrEqualTo(0)) {
        currentLevel++;
        if (currentLevel <= 10) {
          currentLevelCapacity = levelCapacities[currentLevel];
        }
      }
    }
  }

  return allocations;
}

Here's an example usage to replicate an scenario

// Generate random participations using BigNumber (no rounding on participations)
function generateRandomParticipations(
  n: number,
  totalParticipation: BigNumber // in SOL
): Record<string, BigNumber> {
  if (n <= 0) return {};

  // Generate random weights
  const weights = Array.from({ length: n }, () => Math.random());
  const weightSum = weights.reduce((a, b) => a + b, 0);

  const result: Record<string, BigNumber> = {};
  let allocatedSum = new BigNumber(0);

  // Allocate proportionally (no rounding on participations)
  for (let i = 0; i < n - 1; i++) {
    const participation = new BigNumber(weights[i])
      .dividedBy(weightSum)
      .multipliedBy(totalParticipation);
    result[`user${i + 1}`] = participation;
    allocatedSum = allocatedSum.plus(participation);
  }

  // Give the last user exactly what's remaining to ensure perfect sum
  result[`user${n}`] = totalParticipation.minus(allocatedSum);

  return result;
}

// Example usage
const totalSOL = new BigNumber(2222);
const randomParticipations = generateRandomParticipations(1000, totalSOL);

console.log("Sample participations:");
Object.keys(randomParticipations)
  .slice(0, 5)
  .forEach((key) => {
    console.log(`${key}: ${randomParticipations[key].toString()} SOL`);
  });

const sum = Object.values(randomParticipations).reduce(
  (a, b) => a.plus(b),
  new BigNumber(0)
);
console.log(`Total participations sum: ${sum.toString()} SOL`);

const allocations = generateParticipantAllocations(
  Object.keys(randomParticipations),
  randomParticipations,
  1293892349853495, // public seed
  totalSOL
);

// Print total sol per level
console.log("Total SOL per level:");
for (const [level, users] of Object.entries(allocations)) {
  const totalInLevel = Object.values(users).reduce(
    (a, b) => a.plus(b),
    new BigNumber(0)
  );
  const userCount = Object.keys(users).length;
  console.log(
    `Level ${level}: ${totalInLevel.toString()} SOL (${userCount} users)`
  );
}

Last updated