Skip to content

Commit 58e9f7c

Browse files
authored
Merge pull request #2296 from 0xCardinalError/feat/gitcoin_passport_plugin
feat: Gitcoin passport
2 parents 0404e29 + 67ea56b commit 58e9f7c

File tree

11 files changed

+273
-2
lines changed

11 files changed

+273
-2
lines changed

.env.example

+3-1
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,9 @@ GIPHY_API_KEY=
505505
# OpenWeather
506506
OPEN_WEATHER_API_KEY= # OpenWeather API key
507507

508-
508+
#GITCOIN Passport
509+
PASSPORT_API_KEY= #Gitcoin Passport key
510+
PASSPORT_SCORER= #Scorer number
509511

510512
# EchoChambers Configuration
511513
ECHOCHAMBERS_API_URL=http://127.0.0.1:3333

agent/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"@elizaos/plugin-flow": "workspace:*",
5151
"@elizaos/plugin-gitbook": "workspace:*",
5252
"@elizaos/plugin-story": "workspace:*",
53+
"@elizaos/plugin-gitcoin-passport": "workspace:*",
5354
"@elizaos/plugin-goat": "workspace:*",
5455
"@elizaos/plugin-lensNetwork": "workspace:*",
5556
"@elizaos/plugin-icp": "workspace:*",

agent/src/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import { flowPlugin } from "@elizaos/plugin-flow";
7070
import { fuelPlugin } from "@elizaos/plugin-fuel";
7171
import { genLayerPlugin } from "@elizaos/plugin-genlayer";
7272
import { giphyPlugin } from "@elizaos/plugin-giphy";
73+
import { gitcoinPassportPlugin } from "@elizaos/plugin-gitcoin-passport";
7374
import { hyperliquidPlugin } from "@elizaos/plugin-hyperliquid";
7475
import { imageGenerationPlugin } from "@elizaos/plugin-image-generation";
7576
import { lensPlugin } from "@elizaos/plugin-lensNetwork";
@@ -825,7 +826,7 @@ export async function createAgent(
825826
getSecret(character, "ABSTRACT_PRIVATE_KEY")
826827
? abstractPlugin
827828
: null,
828-
getSecret(character, "B2_PRIVATE_KEY") ? b2Plugin: null,
829+
getSecret(character, "B2_PRIVATE_KEY") ? b2Plugin : null,
829830
getSecret(character, "BINANCE_API_KEY") &&
830831
getSecret(character, "BINANCE_SECRET_KEY")
831832
? binancePlugin
@@ -868,6 +869,9 @@ export async function createAgent(
868869
getSecret(character, "LETZAI_API_KEY") ? letzAIPlugin : null,
869870
getSecret(character, "STARGAZE_ENDPOINT") ? stargazePlugin : null,
870871
getSecret(character, "GIPHY_API_KEY") ? giphyPlugin : null,
872+
getSecret(character, "PASSPORT_API_KEY")
873+
? gitcoinPassportPlugin
874+
: null,
871875
getSecret(character, "GENLAYER_PRIVATE_KEY")
872876
? genLayerPlugin
873877
: null,
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# `@elizaos/plugin-passport`
2+
3+
This plugin provides actions for interacting with Gitcoin passport
4+
https://docs.passport.xyz/building-with-passport/passport-api/overview
5+
6+
---
7+
8+
## Installation
9+
10+
Just add it under your character profile in plugins as
11+
12+
```
13+
"plugins": [
14+
"@elizaos/plugin-gitcoin-passport"
15+
],
16+
```
17+
18+
## Configuration
19+
20+
Getting Your API Key
21+
22+
1. Log in to the developer portal: Go to developer.passport.xyz and log in to your account by connecting your wallet.
23+
2. Navigate to the API Keys section: After logging in, go to the "API Keys" section.
24+
3. Generate an API key: Click on the "+ Create a Key" button to generate a unique API key for your account.
25+
4. Store your API key securely: Store your API key in a secure place, as it will be used to access the Passport API.
26+
27+
Getting your Scorer ID
28+
29+
1. Log in to the Developer Portal: Go to developer.passport.xyz and log in to your account by connecting your wallet.
30+
2. Navigate to the Scorer section: After logging in, go to the "Scorer" section
31+
3. Create a Scorer: Click on the "+ Create a Scorer" button and input a Scorer name and description. Make sure you use the Unique Humanity scorer, and not the Binary scorer.
32+
4. Find your Scorer ID: Click on the newly created Scorer and you will see the Scorer ID in the page URL.
33+
Example: https://developer.passport.xyz/dashboard/scorer/{scorer_id}
34+
35+
## Usage
36+
37+
Results are saved as message and agents can retrive it from there for different use cases.
38+
Default passport treshold of 20 is used, but you can pick your own value and match it agains that
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import eslintGlobalConfig from "../../eslint.config.mjs";
2+
3+
export default [...eslintGlobalConfig];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "@elizaos/plugin-gitcoin-passport",
3+
"version": "0.1.7-alpha.2",
4+
"main": "dist/index.js",
5+
"type": "module",
6+
"types": "dist/index.d.ts",
7+
"dependencies": {
8+
"@elizaos/core": "workspace:*",
9+
"tsup": "8.3.5"
10+
},
11+
"scripts": {
12+
"build": "tsup --format esm --dts",
13+
"dev": "tsup --format esm --dts --watch",
14+
"test": "vitest run",
15+
"lint": "eslint --fix --cache ."
16+
},
17+
"peerDependencies": {
18+
"whatwg-url": "7.1.0"
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import {
2+
Action,
3+
elizaLogger,
4+
IAgentRuntime,
5+
Memory,
6+
HandlerCallback,
7+
State,
8+
getEmbeddingZeroVector,
9+
Content,
10+
composeContext,
11+
generateMessageResponse,
12+
ModelClass,
13+
} from "@elizaos/core";
14+
15+
interface PassportScore {
16+
address: string;
17+
score: string;
18+
threshold: string;
19+
passing_score: string;
20+
}
21+
22+
const createTokenMemory = async (
23+
runtime: IAgentRuntime,
24+
_message: Memory,
25+
formattedOutput: string
26+
) => {
27+
const memory: Memory = {
28+
userId: _message.userId,
29+
agentId: _message.agentId,
30+
roomId: _message.roomId,
31+
content: { text: formattedOutput },
32+
createdAt: Date.now(),
33+
embedding: getEmbeddingZeroVector(),
34+
};
35+
await runtime.messageManager.createMemory(memory);
36+
};
37+
38+
export const addressTemplate = `From previous sentence extract only the Ethereum address being asked about.
39+
Respond with a JSON markdown block containing only the extracted value:
40+
41+
\`\`\`json
42+
{
43+
"address": string | null
44+
}
45+
\`\`\`
46+
`;
47+
48+
export const getPassportScoreAction: Action = {
49+
name: "GET_PASSPORT_SCORE",
50+
description: "Get score from Passport API for an address",
51+
validate: async (runtime: IAgentRuntime, _message: Memory) => {
52+
elizaLogger.log("Validating runtime for GET_PASSPORT_SCORE...");
53+
const apiKey = runtime.getSetting("PASSPORT_API_KEY");
54+
const scorerId = runtime.getSetting("PASSPORT_SCORER");
55+
if (!apiKey || !scorerId) {
56+
elizaLogger.error(
57+
"Missing PASSPORT_API_KEY or PASSPORT_SCORER settings"
58+
);
59+
return false;
60+
}
61+
return true;
62+
},
63+
handler: async (
64+
runtime: IAgentRuntime,
65+
_message: Memory,
66+
state: State,
67+
_options: any,
68+
callback: HandlerCallback
69+
) => {
70+
elizaLogger.log("Starting GET_PASSPORT_SCORE handler...");
71+
const apiKey = runtime.getSetting("PASSPORT_API_KEY");
72+
const scorerId = runtime.getSetting("PASSPORT_SCORER");
73+
74+
if (!state) {
75+
state = (await runtime.composeState(_message)) as State;
76+
} else {
77+
state = await runtime.updateRecentMessageState(state);
78+
}
79+
80+
const context = composeContext({
81+
state,
82+
template: `${_message.content.text}\n${addressTemplate}`,
83+
});
84+
85+
const addressRequest = await generateMessageResponse({
86+
runtime,
87+
context,
88+
modelClass: ModelClass.SMALL,
89+
});
90+
91+
const address = addressRequest.address as string;
92+
93+
if (!address) {
94+
callback({ text: "Address is required." }, []);
95+
return;
96+
}
97+
98+
try {
99+
const response = await fetch(
100+
`https://api.passport.xyz/v2/stamps/${scorerId}/score/${address}`,
101+
{
102+
method: "GET",
103+
headers: {
104+
"X-API-KEY": apiKey,
105+
accept: "application/json",
106+
},
107+
}
108+
);
109+
110+
if (!response.ok) {
111+
throw new Error(`HTTP error! status: ${response.status}`);
112+
}
113+
114+
const data: PassportScore = await response.json();
115+
const formattedOutput = `Address: ${data.address}\nScore: ${data.score}${data.passing_score ? "\nScore is above threshold" : `\nScore is below threshold (${data.threshold})`}`;
116+
117+
await createTokenMemory(runtime, _message, formattedOutput);
118+
119+
callback({ text: formattedOutput }, []);
120+
} catch (error) {
121+
elizaLogger.error("Error fetching Passport score:", error);
122+
callback(
123+
{
124+
text: "Failed to fetch Passport score. Please check the logs for more details.",
125+
},
126+
[]
127+
);
128+
}
129+
},
130+
examples: [],
131+
similes: [
132+
"GET_PASSPORT_SCORE",
133+
"FETCH_PASSPORT_SCORE",
134+
"CHECK_PASSPORT_SCORE",
135+
"VIEW_PASSPORT_SCORE",
136+
],
137+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export * from "./actions/getScore";
2+
3+
import type { Plugin } from "@elizaos/core";
4+
import { getPassportScoreAction } from "./actions/getScore";
5+
6+
export const gitcoinPassportPlugin: Plugin = {
7+
name: "passport",
8+
description: "Gitcoin passport integration plugin",
9+
providers: [],
10+
evaluators: [],
11+
services: [],
12+
actions: [getPassportScoreAction],
13+
};
14+
15+
export default gitcoinPassportPlugin;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"extends": "../core/tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"rootDir": "./src",
6+
"typeRoots": [
7+
"./node_modules/@types",
8+
"./src/types"
9+
],
10+
"declaration": true
11+
},
12+
"include": [
13+
"src"
14+
]
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { defineConfig } from "tsup";
2+
3+
export default defineConfig({
4+
entry: ["src/index.ts"],
5+
outDir: "dist",
6+
sourcemap: true,
7+
clean: true,
8+
format: ["esm"], // Ensure you're targeting CommonJS
9+
external: [
10+
"dotenv", // Externalize dotenv to prevent bundling
11+
"fs", // Externalize fs to use Node.js built-in module
12+
"path", // Externalize other built-ins if necessary
13+
"@reflink/reflink",
14+
"@node-llama-cpp",
15+
"https",
16+
"http",
17+
"agentkeepalive",
18+
"viem",
19+
"@lifi/sdk",
20+
],
21+
});

pnpm-lock.yaml

+15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)