Skip to content

Commit

Permalink
feat: add metadata update flow and final tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
kaladinlight committed Jul 22, 2024
1 parent c149033 commit a9faad7
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 36 deletions.
62 changes: 46 additions & 16 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { isEpochDistributionStarted } from './file'
import { IPFS } from './ipfs'
import { error, info, success, warn } from './logging'
import { create, recoverKeystore } from './mnemonic'
import { Epoch } from './types'
import { EpochWithHash } from './types'
import { Wallet } from './wallet'

const processEpoch = async () => {
Expand All @@ -21,7 +21,7 @@ const processEpoch = async () => {

const month = MONTHS[new Date(metadata.epochStartTimestamp).getUTCMonth()]

info(`Processing Epoch #${metadata.epoch} for ${month} distribution.`)
info(`Processing rFOX Epoch #${metadata.epoch} for ${month} distribution.`)

const now = Date.now()
if (metadata.epochEndTimestamp > now) {
Expand Down Expand Up @@ -88,7 +88,7 @@ const processEpoch = async () => {

const nextEpochStartDate = new Date(metadata.epochEndTimestamp + 1)

await ipfs.updateMetadata(metadata, {
const hash = await ipfs.updateMetadata(metadata, {
epoch: { number: metadata.epoch, hash: epochHash },
metadata: {
epoch: metadata.epoch + 1,
Expand All @@ -97,6 +97,8 @@ const processEpoch = async () => {
},
})

if (!hash) return

success(`rFOX Epoch #${metadata.epoch} has been processed!`)

info(
Expand All @@ -107,7 +109,7 @@ const processEpoch = async () => {
const run = async () => {
const ipfs = await IPFS.new()

const epoch = await ipfs.getEpoch()
const epoch = await ipfs.getEpochFromMetadata()

if (isEpochDistributionStarted(epoch.number)) {
const confirmed = await prompts.confirm({
Expand Down Expand Up @@ -138,10 +140,12 @@ const run = async () => {
await processDistribution(epoch, wallet, ipfs)
}

const recover = async (epoch?: Epoch) => {
const recover = async (epoch?: EpochWithHash) => {
const ipfs = await IPFS.new()

if (!epoch) epoch = await ipfs.getEpoch()
if (!epoch) {
epoch = await ipfs.getEpochFromMetadata()
}

const keystoreFile = path.join(RFOX_DIR, `keystore_epoch-${epoch.number}.txt`)
const mnemonic = await recoverKeystore(keystoreFile)
Expand All @@ -151,14 +155,33 @@ const recover = async (epoch?: Epoch) => {
await processDistribution(epoch, wallet, ipfs)
}

const processDistribution = async (epoch: Epoch, wallet: Wallet, ipfs: IPFS) => {
const update = async () => {
const ipfs = await IPFS.new()

const metadata = await ipfs.getMetadata('update')
const hash = await ipfs.updateMetadata(metadata)

if (!hash) return

success(`rFOX metadata has been updated!`)

info(
'Please update the rFOX Wiki (https://github.com/shapeshift/rFOX/wiki/rFOX-Metadata) and notify the DAO accordingly. Thanks!',
)
}

const processDistribution = async (epoch: EpochWithHash, wallet: Wallet, ipfs: IPFS) => {
await wallet.fund(epoch)
const processedEpoch = await wallet.distribute(epoch)

const processedEpochHash = await ipfs.addEpoch(processedEpoch)
const metadata = await ipfs.getMetadata('update')
const metadata = await ipfs.getMetadata('process')

await ipfs.updateMetadata(metadata, { epoch: { number: processedEpoch.number, hash: processedEpochHash } })
const hash = await ipfs.updateMetadata(metadata, {
epoch: { number: processedEpoch.number, hash: processedEpochHash },
})

if (!hash) return

success(`rFOX reward distribution for Epoch #${processedEpoch.number} has been completed!`)

Expand All @@ -183,23 +206,28 @@ const main = async () => {
}
}

const choice = await prompts.select<'process' | 'run' | 'recover'>({
const choice = await prompts.select<'process' | 'run' | 'recover' | 'update'>({
message: 'What do you want to do?',
choices: [
{
name: 'Process rFox epoch',
name: 'Process rFOX epoch',
value: 'process',
description: 'Start here to process a completed rFox epoch',
description: 'Start here to process an rFOX epoch.',
},
{
name: 'Run rFox distribution',
name: 'Run rFOX distribution',
value: 'run',
description: 'Start here to begin running a new rFox rewards distribution',
description: 'Start here to run an rFOX rewards distribution.',
},
{
name: 'Recover rFox distribution',
name: 'Recover rFOX distribution',
value: 'recover',
description: 'Start here to recover an in progress rFox rewards distribution',
description: 'Start here to recover an rFOX rewards distribution.',
},
{
name: 'Update rFOX metadata',
value: 'update',
description: 'Start here to update an rFOX metadata.',
},
],
})
Expand All @@ -211,6 +239,8 @@ const main = async () => {
return run()
case 'recover':
return recover()
case 'update':
return update()
default:
error(`Invalid choice: ${choice}, exiting.`)
process.exit(1)
Expand Down
155 changes: 144 additions & 11 deletions cli/src/ipfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import PinataClient from '@pinata/sdk'
import axios from 'axios'
import BigNumber from 'bignumber.js'
import { error, info } from './logging'
import { Epoch, RFOXMetadata, RewardDistribution } from './types'
import { Epoch, EpochWithHash, RFOXMetadata, RewardDistribution } from './types'
import { MONTHS } from './constants'

const PINATA_API_KEY = process.env['PINATA_API_KEY']
const PINATA_SECRET_API_KEY = process.env['PINATA_SECRET_API_KEY']
Expand Down Expand Up @@ -98,10 +99,12 @@ export class IPFS {
}
}

async getEpoch(): Promise<Epoch> {
const hash = await prompts.input({
message: 'What is the IPFS hash for the rFOX reward distribution you want to process? ',
})
async getEpoch(hash?: string): Promise<EpochWithHash> {
if (!hash) {
hash = await prompts.input({
message: 'What is the IPFS hash for the rFOX epoch you want to process? ',
})
}

try {
const { data } = await axios.get(`${PINATA_GATEWAY_URL}/ipfs/${hash}`, {
Expand All @@ -111,6 +114,7 @@ export class IPFS {
})

if (isEpoch(data)) {
const month = MONTHS[new Date(data.startTimestamp).getUTCMonth()]
const totalAddresses = Object.keys(data.distributionsByStakingAddress).length
const totalRewards = Object.values(data.distributionsByStakingAddress)
.reduce((prev, distribution) => {
Expand All @@ -120,12 +124,12 @@ export class IPFS {
.toFixed()

info(
`Processing rFOX reward distribution for Epoch #${data.number}:\n - Total Rewards: ${totalRewards} RUNE\n - Total Addresses: ${totalAddresses}`,
`Running ${month} rFOX reward distribution for Epoch #${data.number}:\n - Total Rewards: ${totalRewards} RUNE\n - Total Addresses: ${totalAddresses}`,
)

return data
return { ...data, hash }
} else {
error(`The contents of IPFS hash (${hash}) are not valid, exiting.`)
error(`The contents of IPFS hash (${hash}) are not valid epoch contents, exiting.`)
process.exit(1)
}
} catch {
Expand Down Expand Up @@ -173,9 +177,113 @@ export class IPFS {
}
}

// TODO: manual update walkthrough
const choice = await prompts.select<'distributionRate' | 'burnRate' | 'treasuryAddress'>({
message: 'What do you want to update?',
choices: [
{
name: 'Distribution Rate',
value: 'distributionRate',
description:
'Update the current percentage of revenue (RUNE) earned by the treasury to be distributed as rewards.',
},
{
name: 'Burn Rate',
value: 'burnRate',
description:
'Update the current percentage of revenue (RUNE) earned by the treasury to be used to buy FOX and then burn it.',
},
{
name: 'Treasury Address',
value: 'treasuryAddress',
description: 'Update the THORChain treasury address used to determine revenue earned by the DAO.',
},
],
})

switch (choice) {
case 'distributionRate': {
const distributionRate = parseFloat(
await prompts.input({
message: `The distribution rate is currently set to ${metadata.distributionRate}, what do you want to update it to? `,
}),
)

return
if (isNaN(distributionRate) || distributionRate < 0 || distributionRate > 1) {
error(`Invalid distribution rate, it must be a number between 0 and 1 (ex. 0.25).`)
return this.updateMetadata(metadata)
}

metadata.distributionRate = distributionRate

break
}
case 'burnRate': {
const burnRate = parseFloat(
await prompts.input({
message: `The burn rate is currently set to ${metadata.burnRate}, what do you want to update it to? `,
}),
)

if (isNaN(burnRate) || burnRate < 0 || burnRate > 1) {
error(`Invalid burn rate, it must be a number between 0 and 1 (ex. 0.25).`)
return this.updateMetadata(metadata)
}

metadata.burnRate = burnRate

break
}
case 'treasuryAddress': {
const treasuryAddress = await prompts.input({
message: `The treasury address is currently set to ${metadata.treasuryAddress}, what do you want to update it to? `,
})

if (!/^thor[a-z0-9]{39}$/.test(treasuryAddress)) {
error(`Invalid treasury address, please check your address and try again.`)
return this.updateMetadata(metadata)
}

metadata.treasuryAddress = treasuryAddress

break
}
default:
error(`Invalid choice: ${choice}, exiting.`)
process.exit(1)
}

const confirmed = await prompts.confirm({
message: `Do you want to update another value?`,
})

if (confirmed) {
return this.updateMetadata(metadata)
} else {
if (metadata.distributionRate + metadata.burnRate > 1) {
error(
`Invalid rates, the sum of the distribution rate and burn rate must be a number between 0 and 1 (ex. 0.5).`,
)
return this.updateMetadata(metadata)
}

info(
`The new metadata values will be:\n - Distribtution Rate: ${metadata.distributionRate}\n - Burn Rate: ${metadata.burnRate}\n - Treasury Address: ${metadata.treasuryAddress}`,
)

const confirmed = await prompts.confirm({
message: `Do you want to update the metadata with the new values?`,
})

if (!confirmed) return

const { IpfsHash } = await this.client.pinJSONToIPFS(metadata, {
pinataMetadata: { name: 'rFoxMetadata.json' },
})

info(`rFOX Metadata IPFS hash: ${IpfsHash}`)

return IpfsHash
}
}

async getMetadata(promptAction: string): Promise<RFOXMetadata> {
Expand All @@ -192,11 +300,36 @@ export class IPFS {

if (isMetadata(data)) return data

error(`The contents of IPFS hash (${hash}) are not valid, exiting.`)
error(`The contents of IPFS hash (${hash}) are not valid metadata contents, exiting.`)
process.exit(1)
} catch {
error(`Failed to get content of IPFS hash (${hash}), exiting.`)
process.exit(1)
}
}

async getEpochFromMetadata(): Promise<EpochWithHash> {
const metadata = await this.getMetadata('process')

const hash = metadata.ipfsHashByEpoch[metadata.epoch - 1]

if (!hash) {
error(`No IPFS hash found for epoch ${metadata.epoch - 1}, exiting.`)
process.exit(1)
}

const epoch = await this.getEpoch(hash)
const month = MONTHS[new Date(epoch.startTimestamp).getUTCMonth()]

switch (epoch.distributionStatus) {
case 'pending':
info(`Running ${month} rFOX reward distribution for Epoch #${epoch.number}.`)
break
case 'complete':
info(`The ${month} rFOX reward distribution for Epoch #${epoch.number} is already complete, exiting.`)
process.exit(0)
}

return epoch
}
}
2 changes: 2 additions & 0 deletions cli/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,5 @@ export type Epoch = {
/** A record of staking address to reward distribution for this epoch */
distributionsByStakingAddress: Record<string, RewardDistribution>
}

export type EpochWithHash = Epoch & { hash: string }
Loading

0 comments on commit a9faad7

Please sign in to comment.