-
Notifications
You must be signed in to change notification settings - Fork 19
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
feat: add the permit-generator github action #69
Changes from 34 commits
25932b2
f0b5b9d
36e3245
b2038b0
c3b3c8d
798aa5c
e397fe0
787979f
2e61614
ba0d046
a225135
f926e74
1620035
0ac5357
917ae26
f828cb3
8540751
133090e
f4fb78c
a7c05c9
7191ee8
011e368
81884f6
04ecb36
f31251d
203433f
153dfe4
55423fb
52cb2c3
d5f79bc
479ce93
b945bdc
41f85f5
b147da4
d19d962
6aaa1ce
5e858dd
99d2feb
8e5cd8f
4aadfc4
2db0505
ff0d9d9
5b20c35
c22e4c2
c78613a
9fe5fda
fe3e60a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
name: Permit Generator | ||
|
||
on: | ||
workflow_dispatch: | ||
inputs: | ||
users_amounts: | ||
description: "A JSON array containing usernames and associated amounts" | ||
required: true | ||
# example: '[{"user1": 100}, {"user2": 150}]' | ||
|
||
jobs: | ||
run: | ||
runs-on: ubuntu-latest | ||
permissions: write-all | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
|
||
- name: Set up Node.js | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: "20.10.0" | ||
|
||
- name: Process permit requests | ||
id: parse | ||
run: | | ||
echo "Received input: ${{ github.event.inputs.users_amounts }}" | ||
npx tsx scripts/github-action-permit-generator.ts permits.txt | ||
shell: bash | ||
env: | ||
X25519_PRIVATE_KEY: ${{ secrets.X25519_PRIVATE_KEY }} | ||
EVM_NETWORK_ID: ${{ secrets.EVM_NETWORK_ID }} | ||
EVM_PRIVATE_KEY: ${{ secrets.EVM_PRIVATE_KEY }} | ||
EVM_TOKEN_ADDRESS: ${{ secrets.EVM_TOKEN_ADDRESS }} | ||
SUPABASE_URL: ${{ secrets.SUPABASE_URL}} | ||
SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
USERS_AMOUNTS: ${{ github.event.inputs.users_amounts }} | ||
|
||
- name: Report by opening an issue | ||
run: | | ||
export PERMITS=$(cat permits.txt) | ||
curl -X POST \ | ||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ | ||
-H "Content-Type: application/json" \ | ||
-d "{\"title\": \"Workflow Dispatch Report\", \"body\": \"$PERMITS\"}" \ | ||
"https://api.github.com/repos/${{ github.repository }}/issues" | ||
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. I suppose this might make sense to just echo or log in the CI instead |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,116 @@ | ||||
import * as github from "@actions/github"; | ||||
import { Octokit } from "@octokit/rest"; | ||||
import { createClient } from "@supabase/supabase-js"; | ||||
import { createAdapters } from "../src/adapters"; | ||||
import { Database } from "../src/adapters/supabase/types/database"; | ||||
import { generatePayoutPermit } from "../src/handlers"; | ||||
import { Context } from "../src/types/context"; | ||||
import { PermitGenerationSettings, PermitRequest } from "../src/types/plugin-input"; | ||||
import { Value } from "@sinclair/typebox/value"; | ||||
import { envSchema } from "../src/types/env"; | ||||
import * as fs from "fs"; | ||||
|
||||
/** | ||||
* Generates all the permits based on the current github workflow dispatch. | ||||
*/ | ||||
export async function generatePermitsFromGithubWorkflowDispatch() { | ||||
const runId = github.context.runId; | ||||
|
||||
// These are necessary to ensure the type checks and tests pass. | ||||
process.env["NFT_MINTER_PRIVATE_KEY"] = ""; | ||||
process.env["NFT_CONTRACT_ADDRESS"] = ""; | ||||
0x4007 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
const env = Value.Decode(envSchema, process.env); | ||||
|
||||
if (!env.EVM_NETWORK_ID) { | ||||
throw new Error("EVM_NETWORK_ID env not provided or empty"); | ||||
} | ||||
if (!env.EVM_PRIVATE_KEY) { | ||||
throw new Error("EVM_PRIVATE_KEY env not provided or empty"); | ||||
} | ||||
if (!env.EVM_TOKEN_ADDRESS) { | ||||
throw new Error("EVM_TOKEN_ADDRESS env not provided or empty"); | ||||
} | ||||
if (!env.USERS_AMOUNTS) { | ||||
throw new Error("USERS_AMOUNTS env not provided or empty"); | ||||
} | ||||
|
||||
console.log(`Received: ${env.USERS_AMOUNTS}`); | ||||
const userAmounts = JSON.parse(env.USERS_AMOUNTS); | ||||
|
||||
// Populate the permitRequests from the user_amounts payload | ||||
|
||||
const permitRequests: PermitRequest[] = userAmounts.flatMap((userObj: { [key: string]: number }) => | ||||
Object.entries(userObj).map(([user, amount]) => ({ | ||||
type: "ERC20", | ||||
username: user, | ||||
amount: amount, | ||||
contributionType: "custom", | ||||
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 seems wrong. I suppose we should pass this in? What is it for again? @whilefoo is it the nft thing? What's the solution in this context? 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. Do you mean contributionType? Yes it was for the NFT. If we are generating ERC20 reward we don't need it 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. Then this line should be removed? 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.
Suggested change
|
||||
tokenAddress: env.EVM_TOKEN_ADDRESS, | ||||
})) | ||||
); | ||||
|
||||
const config: PermitGenerationSettings = { | ||||
evmNetworkId: Number(env.EVM_NETWORK_ID), | ||||
evmPrivateEncrypted: env.EVM_PRIVATE_KEY, | ||||
permitRequests: permitRequests, | ||||
runId: runId, | ||||
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.
Suggested change
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. Removed. |
||||
}; | ||||
|
||||
const octokit = new Octokit({ auth: env.GITHUB_TOKEN }); | ||||
const supabaseClient = createClient<Database>(env.SUPABASE_URL, env.SUPABASE_KEY); | ||||
|
||||
const context: Context = { | ||||
eventName: "workflow_dispatch", | ||||
config: config, | ||||
octokit, | ||||
payload: userAmounts, | ||||
env, | ||||
logger: { | ||||
debug(message: unknown, ...optionalParams: unknown[]) { | ||||
console.debug(message, ...optionalParams); | ||||
}, | ||||
info(message: unknown, ...optionalParams: unknown[]) { | ||||
console.log(message, ...optionalParams); | ||||
}, | ||||
warn(message: unknown, ...optionalParams: unknown[]) { | ||||
console.warn(message, ...optionalParams); | ||||
}, | ||||
error(message: unknown, ...optionalParams: unknown[]) { | ||||
console.error(message, ...optionalParams); | ||||
}, | ||||
fatal(message: unknown, ...optionalParams: unknown[]) { | ||||
console.error(message, ...optionalParams); | ||||
}, | ||||
}, | ||||
adapters: {} as ReturnType<typeof createAdapters>, | ||||
}; | ||||
|
||||
context.adapters = createAdapters(supabaseClient, context); | ||||
|
||||
const permits = await generatePayoutPermit(context, config.permitRequests); | ||||
await returnDataToKernel(env.GITHUB_TOKEN, "todo_state", permits); | ||||
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 why you are returning data to kernel if this workflow is manually triggered by someone 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. I initially thought it was meant to be part of the process. Should I remove it? 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. Because this script is only called manually and not by the kernel |
||||
const out = Buffer.from(JSON.stringify(permits)).toString("base64"); | ||||
fs.writeFile(process.argv[2], out, (err) => { | ||||
if (err) { | ||||
throw err; | ||||
} | ||||
}); | ||||
} | ||||
|
||||
async function returnDataToKernel(repoToken: string, stateId: string, output: object) { | ||||
const octokit = new Octokit({ auth: repoToken }); | ||||
await octokit.repos.createDispatchEvent({ | ||||
owner: github.context.repo.owner, | ||||
repo: github.context.repo.repo, | ||||
event_type: "return_data_to_ubiquibot_kernel", | ||||
client_payload: { | ||||
state_id: stateId, | ||||
output: JSON.stringify(output), | ||||
}, | ||||
}); | ||||
} | ||||
generatePermitsFromGithubWorkflowDispatch() | ||||
.then((result) => console.log(`result: ${result}`)) | ||||
.catch((error) => { | ||||
console.error(error); | ||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,8 +15,11 @@ export class Wallet extends Super { | |
throw error; | ||
} | ||
|
||
console.info("Successfully fetched wallet", { userId, address: data.wallets?.address }); | ||
return data.wallets?.address; | ||
// Check if wallets is an array, if so, return the first element's address | ||
const address = Array.isArray(data.wallets) ? data.wallets[0]?.address : data.wallets?.address; | ||
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. There should only be a single wallet associated per user. Why did you check for arrays 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. I keep getting an array here. Even though the docs say 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. 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. Can't you use 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. Still, keep getting an array. async getWalletByUserId(userId: number) {
const { data, error } = await this.supabase.from("users").select("wallets(*)").eq("id", userId).single();
if (error) {
console.error("Failed to get wallet", { userId, error });
throw error;
}
console.info("Result: ", { wallets: data.wallets });
// Check if wallets is an array, if so, return the first element's address
const address = Array.isArray(data.wallets) ? data.wallets[0]?.address : data.wallets?.address;
console.info("Successfully fetched wallet", { userId, address: address });
return address;
} Results after run:
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. yes it's correct for that to be an array because it's querying an user with wallets as a relation. if you wanted to get just one record you would need to query the wallet table directly 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. @whilefoo The code is already in the base branch but isn't working without the changes in this PR. Should I move the hotfix to a separate PR? |
||
|
||
console.info("Successfully fetched wallet", { userId, address: address }); | ||
return address; | ||
} | ||
|
||
async upsertWallet(userId: number, address: string) { | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -54,6 +54,8 @@ export async function generateErc20PermitSignature( | |||||
issueNodeId = contextOrPayload.payload.issue.node_id; | ||||||
} else if ("pull_request" in contextOrPayload.payload) { | ||||||
issueNodeId = contextOrPayload.payload.pull_request.node_id; | ||||||
} else if (contextOrPayload.config.runId) { | ||||||
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.
Suggested change
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. Thanks for the review, removed. |
||||||
issueNodeId = contextOrPayload.config.runId.toString(); | ||||||
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. The node ID should not be the CI run ID? This doesnt make any sense to me 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. Because when you are manually generating a permit with Action workflow dispatch, it's not associated to any issue so you have to use a unique nonce - that could be the run ID or just a uuid |
||||||
} else { | ||||||
throw new Error("Issue Id is missing"); | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ export const permitGenerationSettingsSchema = T.Object({ | |
evmNetworkId: T.Number(), | ||
evmPrivateEncrypted: T.String(), | ||
permitRequests: T.Array(permitRequestSchema), | ||
runId: T.Optional(T.Number()), | ||
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. What about this line @whilefoo 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. I'm not sure why this is needed 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 is used to generate a unique nonce value; please refer to these lines: 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. 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 makes sense but just using |
||
}); | ||
|
||
export type PermitGenerationSettings = StaticDecode<typeof permitGenerationSettingsSchema>; |
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.
Rename this to payment requests