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
3 changes: 1 addition & 2 deletions cli/sample.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
INFURA_API_KEY=
PINATA_JWT=
PINATA_GATEWAY_URL=
PINATA_GATEWAY_API_KEY=
UNCHAINED_URL=https://api.thorchain.shapeshift.com
PINATA_GATEWAY_API_KEY=
109 changes: 33 additions & 76 deletions cli/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as prompts from '@inquirer/prompts'
import axios, { isAxiosError } from 'axios'
import BigNumber from 'bignumber.js'
import ora, { Ora } from 'ora'
import { Address, PublicClient, createPublicClient, getAddress, getContract, http, parseAbi } from 'viem'
import { Address, PublicClient, createPublicClient, getContract, http, parseAbi } from 'viem'
import { arbitrum } from 'viem/chains'
import { stakingV1Abi } from '../generated/abi'
import { RFOX_REWARD_RATE } from './constants'
Expand All @@ -15,54 +15,30 @@ if (!INFURA_API_KEY) {
process.exit(1)
}

const UNCHAINED_URL = process.env['UNCHAINED_URL']
if (!UNCHAINED_URL) {
error('UNCHAINED_URL not set. Please make sure you copied the sample.env and filled out your .env file.')
process.exit(1)
}

const AVERAGE_BLOCK_TIME_BLOCKS = 1000
const THORCHAIN_PRECISION = 8
const TOKEN_PRECISION = 18
const UNIV2_ETH_FOX_LP_TOKEN: Address = '0x5F6Ce0Ca13B87BD738519545d3E018e70E339c24'

export const ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS_FOX: Address = '0xaC2a4fD70BCD8Bab0662960455c363735f0e2b56'
export const ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS_UNIV2_ETH_FOX: Address = '0x83B51B7605d2E277E03A7D6451B1efc0e5253A2F'

export const stakingContracts = [
ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS_FOX,
ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS_UNIV2_ETH_FOX,
]
export const stakingContracts = [ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS_FOX]

type Revenue = {
addresses: string[]
amount: string
revenue: Record<string, string>
}

type Pool = {
balance_asset: string
balance_rune: string
asset_tor_price: string
totalUsd: number
byService: Record<string, number>
}

type Price = {
assetPriceUsd: Record<string, string>
runePriceUsd: string
rewardAssetPriceUsd: string
}

type ClosingState = {
rewardUnits: bigint
totalRewardUnits: bigint
runeAddress: string
rewardAddress: string
}

type ClosingStateByStakingAddress = Record<string, ClosingState>

const toPrecision = (value: string | number | bigint, precision: number) => {
return new BigNumber(value.toString()).div(new BigNumber(10).pow(precision))
}

export class Client {
private rpc: PublicClient

Expand Down Expand Up @@ -150,9 +126,12 @@ export class Client {

async getRevenue(startTimestamp: number, endTimestamp: number): Promise<Revenue> {
try {
const { data } = await axios.get<Revenue>(
`${UNCHAINED_URL}/api/v1/affiliate/revenue?start=${startTimestamp}&end=${endTimestamp}`,
)
const { data } = await axios.get<Revenue>('https://revenue.shapeshift.com/api/v1/affiliate/revenue', {
params: {
startDate: new Date(startTimestamp).toISOString().split('T')[0],
endDate: new Date(endTimestamp).toISOString().split('T')[0],
},
})
return data
} catch (err) {
if (isAxiosError(err)) {
Expand All @@ -168,47 +147,27 @@ export class Client {
}

async getPrice(): Promise<Price> {
try {
const { data: ethPool } = await axios.get<Pool>(`${UNCHAINED_URL}/lcd/thorchain/pool/ETH.ETH`)
const url = 'https://api.proxy.shapeshift.com/api/v1/markets/simple/price'

const { data: foxPool } = await axios.get<Pool>(
`${UNCHAINED_URL}/lcd/thorchain/pool/ETH.FOX-0XC770EEFAD204B5180DF6A14EE197D99D808EE52D`,
)
try {
const {
data: { 'usd-coin': usdc },
} = await axios.get<{ 'usd-coin': { usd: number } }>(url, { params: { vs_currencies: 'usd', ids: 'usd-coin' } })

const {
data: { 'shapeshift-fox-token': fox },
} = await axios.get<{ 'shapeshift-fox-token': { usd: number } }>(url, {
params: { vs_currencies: 'usd', ids: 'shapeshift-fox-token' },
})

const ethPriceUsd = toPrecision(ethPool.asset_tor_price, THORCHAIN_PRECISION).toFixed(THORCHAIN_PRECISION)
const foxPriceUsd = toPrecision(foxPool.asset_tor_price, THORCHAIN_PRECISION).toFixed(THORCHAIN_PRECISION)
const runeInAsset = new BigNumber(foxPool.balance_asset).div(foxPool.balance_rune)
const runePriceUsd = runeInAsset.times(foxPriceUsd).toFixed(THORCHAIN_PRECISION)

const address = UNIV2_ETH_FOX_LP_TOKEN
const abi = parseAbi([
'function getReserves() view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast)',
'function totalSupply() view returns (uint256 totalSupply)',
])

const reserves = await this.rpc.readContract({ address, abi, functionName: 'getReserves' })
const totalSupplyBaseUnit = await this.rpc.readContract({ address, abi, functionName: 'totalSupply' })

// reserve0 is for token0 (WETH): https://arbiscan.io/address/0x5F6Ce0Ca13B87BD738519545d3E018e70E339c24#readContract#F15
// reserve1 is for token1 (FOX): https://arbiscan.io/address/0x5F6Ce0Ca13B87BD738519545d3E018e70E339c24#readContract#F16
const [reserve0, reserve1] = reserves.map(reserveBaseUnit => toPrecision(reserveBaseUnit, TOKEN_PRECISION))
const totalSupply = toPrecision(totalSupplyBaseUnit, TOKEN_PRECISION)
const uniV2FoxEthPriceUsd = reserve0
.times(ethPriceUsd)
.plus(reserve1.times(foxPriceUsd))
.div(totalSupply)
.toFixed(THORCHAIN_PRECISION)

info(`Current RUNE price (USD): ${runePriceUsd}`)
info(`Current FOX price (USD): ${foxPriceUsd}`)
info(`Current Uniswapv2 LP ETH/FOX price (USD): ${uniV2FoxEthPriceUsd}`)
info(`Current USDC price (USD): ${usdc.usd}`)
info(`Current FOX price (USD): ${fox.usd}`)

return {
assetPriceUsd: {
[ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS_FOX]: foxPriceUsd,
[ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS_UNIV2_ETH_FOX]: uniV2FoxEthPriceUsd,
[ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS_FOX]: String(fox.usd),
},
runePriceUsd,
rewardAssetPriceUsd: String(usdc.usd),
}
} catch (err) {
if (isAxiosError(err)) {
Expand Down Expand Up @@ -237,8 +196,6 @@ export class Client {

const closingStateByStakingAddress: Record<string, ClosingState> = {}
for await (const address of addresses) {
const [, , , , runeAddress] = await contract.read.stakingInfo([getAddress(address)], { blockNumber: endBlock })

const totalRewardUnitsPrevEpoch = await contract.read.earned([address], {
blockNumber: prevEpochEndBlock,
})
Expand All @@ -250,7 +207,7 @@ export class Client {
await new Promise(resolve => setTimeout(resolve, 1000))
if (rewardUnits <= 0) continue

closingStateByStakingAddress[address] = { rewardUnits, totalRewardUnits, runeAddress }
closingStateByStakingAddress[address] = { rewardUnits, totalRewardUnits, rewardAddress: address }
}

return closingStateByStakingAddress
Expand All @@ -266,7 +223,7 @@ export class Client {
)

const distributionsByStakingAddress: Record<string, RewardDistribution> = {}
for await (const [address, { rewardUnits, totalRewardUnits, runeAddress }] of Object.entries(
for await (const [address, { rewardUnits, totalRewardUnits, rewardAddress }] of Object.entries(
closingStateByStakingAddress,
)) {
const percentageShare = BigNumber(rewardUnits.toString()).div(totalEpochRewardUnits.toString())
Expand All @@ -276,7 +233,7 @@ export class Client {
amount,
rewardUnits: rewardUnits.toString(),
totalRewardUnits: totalRewardUnits.toString(),
rewardAddress: runeAddress,
rewardAddress,
txId: '',
}
}
Expand Down Expand Up @@ -336,7 +293,7 @@ export class Client {

spinner.succeed()

info(`Total addresses receiving rewards: ${addresses.length}`)
info(`Total addresses receiving rewards: ${Object.keys(distributionsByStakingAddress).length}`)

const epochRewardUnits = RFOX_REWARD_RATE * secondsInEpoch
const epochRewardUnitsMargin = BigNumber(epochRewardUnits.toString()).times(0.0001)
Expand All @@ -361,8 +318,8 @@ export class Client {
'The total reward distribution calculated for all stakers is outside of the expected .01% margin of the total rewards to be distributed.',
)

info(`Total Distribution Calculated: ${totalEpochDistribution.div(100000000).toFixed()} RUNE`)
info(`Total Epoch Distribution: ${totalDistribution.div(100000000).toFixed()} RUNE`)
info(`Total Distribution Calculated: ${totalEpochDistribution.div(1000000).toFixed()} USDC`)
info(`Total Epoch Distribution: ${totalDistribution.div(1000000).toFixed()} USDC`)

const confirmed = await prompts.confirm({ message: 'Do you want to continue? ' })

Expand Down
8 changes: 8 additions & 0 deletions cli/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import type { Address } from 'viem'

export const UNCHAINED_THORCHAIN_URL = 'https://api.thorchain.shapeshift.com'
export const UNCHAINED_THORCHAIN_V1_URL = 'https://api.thorchain-v1.shapeshift.com'

export const ARBITRUM_SAFE_ADDRESS: Address = '0x38276553F8fbf2A027D901F8be45f00373d8Dd48'
export const ARBITRUM_USDC_ADDRESS: Address = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'

export const RFOX_REWARD_RATE = 1n * 10n ** 27n

export const MONTHS = [
Expand Down
Loading
Loading