Skip to content
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

ethernaut-optigov projects task #37

Open
wants to merge 5 commits into
base: feature/optigov
Choose a base branch
from
Open
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
62 changes: 62 additions & 0 deletions packages/ethernaut-optigov/src/internal/agora/Agora.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const axios = require('axios')
const EthernautCliError = require('ethernaut-common/src/error/error')
const debug = require('ethernaut-common/src/ui/debug')

const API_BASE_URL = 'https://vote.optimism.io/api/v1'
const AGORA_API_KEY = process.env.AGORA_API_KEY

class Agora {
constructor() {
this.apiKey = AGORA_API_KEY
this.apiBaseUrl = API_BASE_URL
this.bearerToken = null
}

// Axios instance setup
createAxiosInstance() {
const headers = {
Authorization: this.bearerToken
? `Bearer ${this.bearerToken}`
: `Bearer ${this.apiKey}`,
}

return axios.create({
baseURL: this.apiBaseUrl,
headers,
})
}

// Handle common API errors
handleError(error) {
if (error.response) {
throw new EthernautCliError(
'ethernaut-optigov',
`Http status error: ${error.response.data}`,
)
} else {
throw new EthernautCliError(
'ethernaut-optigov',
`Http status error: ${error.message}`,
)
}
}

// common method for getting API spec
async getSpec() {
try {
const axiosInstance = this.createAxiosInstance()
const response = await axiosInstance.get('/spec')
debug.log(`Spec: ${response.data}`, 'ethernaut-optigov')
return response.data
} catch (error) {
this.handleError(error)
}
}

// Set the bearer token after authentication
setBearerToken(token) {
this.bearerToken = token
}
}

module.exports = Agora
59 changes: 18 additions & 41 deletions packages/ethernaut-optigov/src/internal/agora/Auth.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,38 @@
const axios = require('axios')
const debug = require('ethernaut-common/src/ui/debug')
const EthernautCliError = require('ethernaut-common/src/error/error')
const API_BASE_URL = 'https://vote.optimism.io/api/v1'

class Auth {
constructor() {}
constructor(agora) {
this.agora = agora
}

// Get a nonce from the Agora API
async getNonce() {
try {
const response = await axios.get(`${API_BASE_URL}/auth/nonce`)

const axiosInstance = this.agora.createAxiosInstance()
const response = await axiosInstance.get('/auth/nonce')
debug.log(`Nonce: ${response.data.nonce}`, 'ethernaut-optigov')
return response.data
return response.data.nonce
} catch (error) {
throw new EthernautCliError(
'ethernaut-optigov',
`Http status error: ${error.message}`,
)
this.agora.handleError(error)
}
}

// Authenticate with the Agora API
async authenticateWithAgora(message, signature, nonce) {
try {
const response = await axios.post(
`${API_BASE_URL}/auth/verify`,
{
message,
signature,
nonce,
},
{
headers: {
'Content-Type': 'application/json',
},
},
)
const axiosInstance = this.agora.createAxiosInstance()
const response = await axiosInstance.post('/auth/verify', {
message,
signature,
nonce,
})

debug.log('Auth Response Status:', response.status)
// save the Bearer token
this.bearerToken = response.data.access_token

// Set the Bearer token for future requests
this.agora.setBearerToken(response.data.access_token)

return response.data.access_token
} catch (error) {
if (error.response) {
// Server responded with a status other than 2xx
throw new EthernautCliError(
'ethernaut-optigov',
`Http status error: ${error.response.data}`,
)
} else {
// Other errors (e.g., network issue)
throw new EthernautCliError(
'ethernaut-optigov',
`Http status error: ${error.message}`,
)
}
this.agora.handleError(error)
}
}
}
Expand Down
44 changes: 44 additions & 0 deletions packages/ethernaut-optigov/src/internal/agora/Projects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const debug = require('ethernaut-common/src/ui/debug')

class Projects {
constructor(agora) {
this.agora = agora
}

async getLatestRound() {
return 5 // Placeholder, you can implement this logic
}

async getProjects({ limit = 10, offset = 0 } = {}) {
try {
const axiosInstance = this.agora.createAxiosInstance()
const response = await axiosInstance.get('/projects', {
params: { limit, offset },
})

debug.log(`Projects: ${response.data}`, 'ethernaut-optigov')
return response.data.data
} catch (error) {
this.agora.handleError(error)
}
}

async getRoundProjects({ roundId, limit = 10, offset = 0 }) {
try {
const axiosInstance = this.agora.createAxiosInstance()
const response = await axiosInstance.get(
`/retrofunding/rounds/${roundId}/projects`,
{
params: { limit, offset },
},
)

debug.log(`Round Projects: ${response.data}`, 'ethernaut-optigov')
return response.data.data
} catch (error) {
this.agora.handleError(error)
}
}
}

module.exports = Projects
97 changes: 97 additions & 0 deletions packages/ethernaut-optigov/src/tasks/Projects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
const types = require('ethernaut-common/src/validation/types')
const output = require('ethernaut-common/src/ui/output')
const Projects = require('../internal/agora/Projects')
const Agora = require('../internal/agora/Agora')

require('../scopes/optigov')
.task(
'projects',
'Prints a list of projects registered in RetroPGF, given specified filters',
)
.addOptionalParam(
'limit',
'The maximum number of proposals to fetch. Defaults to 10.',
10,
types.int,
)
.addOptionalParam(
'offset',
'The number of proposals to skip before starting to fetch. Defaults to 0.',
0,
types.int,
)
.addParam(
'round',
'The round number to query. Defaults to "latest". Can also be "any" to query all rounds.',
'latest',
types.string,
)
.addOptionalParam(
'name',
'A filter to apply to the project names',
undefined,
types.string,
)
.addOptionalParam(
'category',
'A filter to apply to the project category',
undefined,
types.string,
)
.setAction(async ({ limit, offset, round, name, category }) => {
try {
const projects = await getProjects(limit, offset, round)

const filteredProjects = filterProjects(projects, name, category)

return output.resultBox(printProjects(filteredProjects), 'Projects')
} catch (err) {
return output.errorBox(err)
}
})

function filterProjects(projects, name, category) {
const nameLower = name?.toLowerCase()
const categoryLower = category?.toLowerCase()

return projects.filter((p) => {
// Filter by name if it's provided
const matchesName = nameLower
? p.name.toLowerCase().includes(nameLower)
: true

// Filter by category if it's provided
const matchesCategory = categoryLower
? p.category.toLowerCase() === categoryLower
: true

return matchesName && matchesCategory
})
}

function printProjects(projects) {
const strs = []

for (const project of projects) {
strs.push(` - ${project.name}: ${project.category}: ${project.description}`)
}

return strs.join('\n\n')
}

async function getProjects(limit, offset, round) {
const agora = new Agora()
const projects = new Projects(agora)

let roundId

if (round === 'latest') {
roundId = await projects.getLatestRound()
}

if (!roundId) {
return await projects.getProjects(limit, offset)
}

return await projects.getRoundProjects({ roundId, limit, offset })
}
6 changes: 4 additions & 2 deletions packages/ethernaut-optigov/src/tasks/login.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const Auth = require('../internal/agora/Auth')
const output = require('ethernaut-common/src/ui/output')
const { createSiweMessage } = require('../internal/Siwe')
const EthernautCliError = require('ethernaut-common/src/error/error')
const Auth = require('../internal/agora/Auth')
const Agora = require('../internal/agora/Agora')

require('../scopes/optigov')
.task(
Expand All @@ -24,7 +25,8 @@ require('../scopes/optigov')
'ethernaut-optigov',
)

const auth = new Auth()
const agora = new Agora()
const auth = new Auth(agora)

const statement = 'Log in to Agoras RetroPGF API with SIWE.'
const nonce = await auth.getNonce()
Expand Down
Loading
Loading