@show-karma/karma-gap-sdk 0.4.15 → 0.4.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursorrules +43 -0
- package/core/abi/AirdropNFT.json +1 -1
- package/core/abi/Allo.json +860 -860
- package/core/abi/AlloRegistry.json +578 -578
- package/core/abi/CommunityResolverABI.json +506 -506
- package/core/abi/Donations.json +251 -251
- package/core/abi/EAS.json +1 -1
- package/core/abi/MultiAttester.json +746 -746
- package/core/abi/ProjectResolver.json +574 -574
- package/core/abi/SchemaRegistry.json +1 -1
- package/core/abi/index.ts +21 -0
- package/core/class/AllGapSchemas.ts +21 -0
- package/core/class/Attestation.ts +429 -0
- package/core/class/Fetcher.ts +224 -0
- package/core/class/GAP.ts +481 -0
- package/core/class/GapSchema.ts +93 -0
- package/core/class/Gelato/{Gelato.js → Gelato.ts} +23 -0
- package/core/class/GrantProgramRegistry/Allo.ts +188 -0
- package/core/class/GrantProgramRegistry/AlloRegistry.ts +101 -0
- package/core/class/GraphQL/AxiosGQL.ts +29 -0
- package/core/class/GraphQL/EASClient.ts +34 -0
- package/core/class/GraphQL/GapEasClient.ts +869 -0
- package/core/class/Schema.ts +659 -0
- package/core/class/SchemaError.ts +42 -0
- package/core/class/contract/GapContract.ts +457 -0
- package/core/class/entities/Community.ts +148 -0
- package/core/class/entities/ContributorProfile.ts +108 -0
- package/core/class/entities/Grant.ts +321 -0
- package/core/class/entities/GrantUpdate.ts +187 -0
- package/core/class/entities/MemberOf.ts +52 -0
- package/core/class/entities/Milestone.ts +898 -0
- package/core/class/entities/Project.ts +672 -0
- package/core/class/entities/ProjectImpact.ts +170 -0
- package/core/class/entities/ProjectMilestone.ts +254 -0
- package/core/class/entities/ProjectPointer.ts +39 -0
- package/core/class/entities/ProjectUpdate.ts +176 -0
- package/core/class/entities/Track.ts +32 -0
- package/core/class/karma-indexer/GapIndexerClient.ts +383 -0
- package/core/class/karma-indexer/api/GapIndexerApi.ts +446 -0
- package/core/class/karma-indexer/api/types.ts +313 -0
- package/core/class/remote-storage/IpfsStorage.ts +76 -0
- package/core/class/remote-storage/RemoteStorage.ts +65 -0
- package/core/class/types/allo.ts +93 -0
- package/core/class/types/attestations.ts +223 -0
- package/core/consts.ts +775 -0
- package/core/scripts/create-grant.ts +102 -0
- package/core/scripts/create-program.ts +43 -0
- package/core/scripts/create-schemas.ts +65 -0
- package/core/scripts/deploy.ts +65 -0
- package/core/scripts/index.ts +1 -0
- package/core/scripts/milestone-multi-grants.ts +125 -0
- package/core/shared/types.ts +13 -0
- package/core/types.ts +224 -0
- package/core/utils/gelato/send-gelato-txn.ts +114 -0
- package/core/utils/gelato/sponsor-handler.ts +77 -0
- package/core/utils/gelato/watch-gelato-txn.ts +67 -0
- package/core/utils/get-date.ts +3 -0
- package/core/utils/get-ipfs-data.ts +13 -0
- package/core/utils/get-web3-provider.ts +18 -0
- package/core/utils/gql-queries.ts +133 -0
- package/core/utils/map-filter.ts +21 -0
- package/core/utils/serialize-bigint.ts +7 -0
- package/core/utils/to-unix.ts +18 -0
- package/create-community-example.ts +119 -0
- package/csv-upload/README.md +74 -0
- package/csv-upload/config.ts +41 -0
- package/csv-upload/example.csv +2 -0
- package/csv-upload/keys.example.json +8 -0
- package/csv-upload/scripts/run.ts +417 -0
- package/csv-upload/types.ts +39 -0
- package/docs/.gitkeep +0 -0
- package/docs/images/attestation-architecture.png +0 -0
- package/docs/images/dfd-get-projects.png +0 -0
- package/gap-schema.yaml +155 -0
- package/milestone-workflow-example.ts +353 -0
- package/package.json +45 -39
- package/readme.md +872 -0
- package/schemas/.gitkeep +0 -0
- package/schemas/GAP-schemas-1692135812877.json +33 -0
- package/test-file-indexer-api.ts +25 -0
- package/tsconfig.json +26 -0
- package/core/abi/index.d.ts +0 -1114
- package/core/abi/index.js +0 -26
- package/core/class/AllGapSchemas.d.ts +0 -9
- package/core/class/AllGapSchemas.js +0 -19
- package/core/class/Attestation.d.ts +0 -173
- package/core/class/Attestation.js +0 -333
- package/core/class/Fetcher.d.ts +0 -175
- package/core/class/Fetcher.js +0 -13
- package/core/class/GAP.d.ts +0 -254
- package/core/class/GAP.js +0 -289
- package/core/class/GapSchema.d.ts +0 -34
- package/core/class/GapSchema.js +0 -62
- package/core/class/GrantProgramRegistry/Allo.d.ts +0 -17
- package/core/class/GrantProgramRegistry/Allo.js +0 -137
- package/core/class/GrantProgramRegistry/AlloRegistry.d.ts +0 -15
- package/core/class/GrantProgramRegistry/AlloRegistry.js +0 -70
- package/core/class/GraphQL/AxiosGQL.d.ts +0 -6
- package/core/class/GraphQL/AxiosGQL.js +0 -25
- package/core/class/GraphQL/EASClient.d.ts +0 -16
- package/core/class/GraphQL/EASClient.js +0 -26
- package/core/class/GraphQL/GapEasClient.d.ts +0 -71
- package/core/class/GraphQL/GapEasClient.js +0 -451
- package/core/class/GraphQL/index.js +0 -19
- package/core/class/Schema.d.ts +0 -233
- package/core/class/Schema.js +0 -488
- package/core/class/SchemaError.d.ts +0 -30
- package/core/class/SchemaError.js +0 -39
- package/core/class/contract/GapContract.d.ts +0 -102
- package/core/class/contract/GapContract.js +0 -285
- package/core/class/entities/Community.d.ts +0 -34
- package/core/class/entities/Community.js +0 -109
- package/core/class/entities/ContributorProfile.d.ts +0 -41
- package/core/class/entities/ContributorProfile.js +0 -69
- package/core/class/entities/Grant.d.ts +0 -54
- package/core/class/entities/Grant.js +0 -223
- package/core/class/entities/GrantUpdate.d.ts +0 -40
- package/core/class/entities/GrantUpdate.js +0 -114
- package/core/class/entities/MemberOf.d.ts +0 -11
- package/core/class/entities/MemberOf.js +0 -33
- package/core/class/entities/Milestone.d.ts +0 -168
- package/core/class/entities/Milestone.js +0 -657
- package/core/class/entities/Project.d.ts +0 -92
- package/core/class/entities/Project.js +0 -418
- package/core/class/entities/ProjectImpact.d.ts +0 -50
- package/core/class/entities/ProjectImpact.js +0 -112
- package/core/class/entities/ProjectMilestone.d.ts +0 -60
- package/core/class/entities/ProjectMilestone.js +0 -174
- package/core/class/entities/ProjectPointer.d.ts +0 -12
- package/core/class/entities/ProjectPointer.js +0 -22
- package/core/class/entities/ProjectUpdate.d.ts +0 -50
- package/core/class/entities/ProjectUpdate.js +0 -110
- package/core/class/entities/Track.d.ts +0 -16
- package/core/class/entities/Track.js +0 -21
- package/core/class/entities/index.js +0 -26
- package/core/class/index.js +0 -26
- package/core/class/karma-indexer/GapIndexerClient.d.ts +0 -66
- package/core/class/karma-indexer/GapIndexerClient.js +0 -207
- package/core/class/karma-indexer/api/GapIndexerApi.d.ts +0 -73
- package/core/class/karma-indexer/api/GapIndexerApi.js +0 -256
- package/core/class/karma-indexer/api/types.d.ts +0 -295
- package/core/class/karma-indexer/api/types.js +0 -2
- package/core/class/remote-storage/IpfsStorage.d.ts +0 -23
- package/core/class/remote-storage/IpfsStorage.js +0 -56
- package/core/class/remote-storage/RemoteStorage.d.ts +0 -41
- package/core/class/remote-storage/RemoteStorage.js +0 -38
- package/core/class/types/allo.d.ts +0 -78
- package/core/class/types/allo.js +0 -2
- package/core/class/types/attestations.d.ts +0 -168
- package/core/class/types/attestations.js +0 -66
- package/core/consts.d.ts +0 -48
- package/core/consts.js +0 -641
- package/core/index.js +0 -24
- package/core/shared/types.d.ts +0 -6
- package/core/shared/types.js +0 -2
- package/core/types.d.ts +0 -131
- package/core/types.js +0 -13
- package/core/utils/gelato/index.js +0 -19
- package/core/utils/gelato/send-gelato-txn.d.ts +0 -55
- package/core/utils/gelato/send-gelato-txn.js +0 -100
- package/core/utils/gelato/sponsor-handler.d.ts +0 -9
- package/core/utils/gelato/sponsor-handler.js +0 -60
- package/core/utils/gelato/watch-gelato-txn.d.ts +0 -7
- package/core/utils/gelato/watch-gelato-txn.js +0 -63
- package/core/utils/get-date.d.ts +0 -1
- package/core/utils/get-date.js +0 -7
- package/core/utils/get-ipfs-data.d.ts +0 -1
- package/core/utils/get-ipfs-data.js +0 -20
- package/core/utils/get-web3-provider.d.ts +0 -2
- package/core/utils/get-web3-provider.js +0 -18
- package/core/utils/gql-queries.d.ts +0 -12
- package/core/utils/gql-queries.js +0 -90
- package/core/utils/index.js +0 -23
- package/core/utils/map-filter.d.ts +0 -8
- package/core/utils/map-filter.js +0 -20
- package/core/utils/serialize-bigint.d.ts +0 -1
- package/core/utils/serialize-bigint.js +0 -8
- package/core/utils/to-unix.d.ts +0 -1
- package/core/utils/to-unix.js +0 -25
- package/index.js +0 -17
- /package/core/class/GraphQL/{index.d.ts → index.ts} +0 -0
- /package/core/class/entities/{index.d.ts → index.ts} +0 -0
- /package/core/class/{index.d.ts → index.ts} +0 -0
- /package/core/{index.d.ts → index.ts} +0 -0
- /package/core/utils/gelato/{index.d.ts → index.ts} +0 -0
- /package/core/utils/{index.d.ts → index.ts} +0 -0
- /package/{core/class/Gelato/Gelato.d.ts → csv-upload/.gitkeep} +0 -0
- /package/{index.d.ts → index.ts} +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { GelatoRelay } from '@gelatonetwork/relay-sdk';
|
|
3
|
+
import { watchGelatoTxn } from './watch-gelato-txn';
|
|
4
|
+
import { GAP } from '../../class/GAP';
|
|
5
|
+
import { Hex } from '../../types';
|
|
6
|
+
|
|
7
|
+
async function sendByUrl(...params: Parameters<GelatoRelay['sponsoredCall']>) {
|
|
8
|
+
const { data } = await axios.post<string>(GAP.gelatoOpts.sponsorUrl, params);
|
|
9
|
+
return data;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Send gelato using an explicit api key.
|
|
14
|
+
*
|
|
15
|
+
* > __This is not safe in the frontend.__
|
|
16
|
+
*
|
|
17
|
+
* @param params
|
|
18
|
+
* @returns Gelato's task id and a wait function.
|
|
19
|
+
*/
|
|
20
|
+
async function sendByApiKey(
|
|
21
|
+
...params: Parameters<GelatoRelay['sponsoredCall']>
|
|
22
|
+
) {
|
|
23
|
+
const { apiKey } = GAP?.gelatoOpts || {};
|
|
24
|
+
|
|
25
|
+
if (!apiKey && params[1] === '{apiKey}')
|
|
26
|
+
throw new Error('No api key provided.');
|
|
27
|
+
|
|
28
|
+
if (apiKey && params[1] === '{apiKey}') params[1] = apiKey;
|
|
29
|
+
|
|
30
|
+
const client = new GelatoRelay();
|
|
31
|
+
const relayResponse = await client.sponsoredCall(...params);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
taskId: relayResponse.taskId,
|
|
35
|
+
/**
|
|
36
|
+
* Waits for the transaction to be confirmed by Gelato.
|
|
37
|
+
* @param ttl interval between checks in ms
|
|
38
|
+
* @returns Txn id
|
|
39
|
+
*/
|
|
40
|
+
wait: (ttl?: number) => watchGelatoTxn(relayResponse.taskId, ttl),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Sends a sponsored call using GelatoRelay
|
|
46
|
+
* @param payload
|
|
47
|
+
* @returns txn hash
|
|
48
|
+
*/
|
|
49
|
+
async function sendGelatoTxn(
|
|
50
|
+
...params: Parameters<GelatoRelay['sponsoredCall']>
|
|
51
|
+
) {
|
|
52
|
+
if (!GAP.gelatoOpts) throw new Error('Gelato opts not set.');
|
|
53
|
+
const { env_gelatoApiKey, sponsorUrl, useGasless, contained } =
|
|
54
|
+
GAP.gelatoOpts;
|
|
55
|
+
|
|
56
|
+
if (!useGasless) throw new Error('Gasless is not enabled.');
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
(sponsorUrl && contained && env_gelatoApiKey) ||
|
|
60
|
+
(sponsorUrl && !contained)
|
|
61
|
+
) {
|
|
62
|
+
return sendByUrl(...params);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { wait } = await sendByApiKey(...params);
|
|
66
|
+
return wait();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Builds the arguments for a sponsored call using GelatoRelay
|
|
71
|
+
* @param data Populated contract call.
|
|
72
|
+
* @param chainId
|
|
73
|
+
* @param target target contract address (Hex)
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
*
|
|
77
|
+
* ```ts
|
|
78
|
+
* const { data } = await contract.populateTransaction.transfer(
|
|
79
|
+
* recipient,
|
|
80
|
+
* amount
|
|
81
|
+
* );
|
|
82
|
+
* const args = buildArgs(data, chainId, target);
|
|
83
|
+
* const txn = sendGelatoTxn(...args);
|
|
84
|
+
* console.log(txn) // 0xabc..
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
function buildArgs(
|
|
88
|
+
/**
|
|
89
|
+
* Populated transaction data.
|
|
90
|
+
*/
|
|
91
|
+
data: string,
|
|
92
|
+
chainId: bigint,
|
|
93
|
+
target: Hex
|
|
94
|
+
): Parameters<GelatoRelay['sponsoredCall']> {
|
|
95
|
+
return [
|
|
96
|
+
{
|
|
97
|
+
data,
|
|
98
|
+
chainId,
|
|
99
|
+
target,
|
|
100
|
+
},
|
|
101
|
+
'{apiKey}', // filled in the api
|
|
102
|
+
{
|
|
103
|
+
retries: 3,
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const Gelato = {
|
|
109
|
+
sendByApiKey,
|
|
110
|
+
sendByUrl,
|
|
111
|
+
buildArgs,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export { sendGelatoTxn, Gelato };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { GelatoRelay } from "@gelatonetwork/relay-sdk";
|
|
2
|
+
import { GAP } from "../../class/GAP";
|
|
3
|
+
import { Gelato, sendGelatoTxn } from "./send-gelato-txn";
|
|
4
|
+
|
|
5
|
+
export interface ApiRequest {
|
|
6
|
+
method: string;
|
|
7
|
+
body: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ApiResponse {
|
|
11
|
+
statusCode: number;
|
|
12
|
+
send: (body: unknown) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const assertionObj = [
|
|
16
|
+
{
|
|
17
|
+
data: /0x[a-fA-F0-9]+/gim,
|
|
18
|
+
chainId: /\d+/,
|
|
19
|
+
target: /0x[a-fA-F0-9]{40}/gim,
|
|
20
|
+
},
|
|
21
|
+
/\{apiKey\}/,
|
|
22
|
+
{
|
|
23
|
+
retries: /\d+/,
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function assert(body: any): body is Parameters<GelatoRelay["sponsoredCall"]> {
|
|
28
|
+
if (!Array.isArray(body) || body.length !== assertionObj.length)
|
|
29
|
+
throw new Error("Invalid request body");
|
|
30
|
+
|
|
31
|
+
assertionObj.forEach((item, index) => {
|
|
32
|
+
// check if objects from assertion Object are present in body
|
|
33
|
+
// and test them using the regexp from the assertion Object
|
|
34
|
+
if (typeof item === "object") {
|
|
35
|
+
Object.entries(item).forEach(([key, value]) => {
|
|
36
|
+
if (!body[index][key]?.toString().match(value))
|
|
37
|
+
throw new Error("Invalid request body");
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// test other items as strings
|
|
41
|
+
else if (!body[index]?.toString().match(item))
|
|
42
|
+
throw new Error("Invalid request body");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function handler(
|
|
49
|
+
req: ApiRequest,
|
|
50
|
+
res: ApiResponse,
|
|
51
|
+
env_gelatoApiKey: string
|
|
52
|
+
) {
|
|
53
|
+
if (req.method !== "POST") {
|
|
54
|
+
res.statusCode = 405;
|
|
55
|
+
res.send("Method not allowed");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const body = req.body as unknown;
|
|
60
|
+
|
|
61
|
+
if (!assert(body)) return;
|
|
62
|
+
|
|
63
|
+
const { [env_gelatoApiKey]: apiKey } = process.env;
|
|
64
|
+
if (!apiKey) throw new Error("Api key not provided.");
|
|
65
|
+
body[1] = apiKey;
|
|
66
|
+
|
|
67
|
+
const result = await Gelato.sendByApiKey(...body);
|
|
68
|
+
const txId = await result.wait();
|
|
69
|
+
res.send(txId);
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
|
+
} catch (error: any) {
|
|
72
|
+
// eslint-disable-next-line no-console
|
|
73
|
+
console.log(error);
|
|
74
|
+
res.statusCode = 400;
|
|
75
|
+
res.send(error.message);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { GelatoRelay } from '@gelatonetwork/relay-sdk';
|
|
2
|
+
|
|
3
|
+
enum TaskState {
|
|
4
|
+
CheckPending = 'CheckPending',
|
|
5
|
+
ExecPending = 'ExecPending',
|
|
6
|
+
ExecSuccess = 'ExecSuccess',
|
|
7
|
+
ExecReverted = 'ExecReverted',
|
|
8
|
+
WaitingForConfirmation = 'WaitingForConfirmation',
|
|
9
|
+
Blacklisted = 'Blacklisted',
|
|
10
|
+
Cancelled = 'Cancelled',
|
|
11
|
+
NotFound = 'NotFound',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Waits for a transaction to be mined at Gelato Network
|
|
16
|
+
* @param taskId
|
|
17
|
+
* @returns
|
|
18
|
+
*/
|
|
19
|
+
async function watchGelatoTxn(taskId: string, ttl = 500): Promise<string> {
|
|
20
|
+
const client = new GelatoRelay();
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const loop = async () => {
|
|
23
|
+
const oneSecond = 1;
|
|
24
|
+
try {
|
|
25
|
+
while (oneSecond) {
|
|
26
|
+
const status = await client.getTaskStatus(taskId);
|
|
27
|
+
// print status :D so we can debug this for now
|
|
28
|
+
// eslint-disable-next-line no-console
|
|
29
|
+
console.log(status);
|
|
30
|
+
if (!status) {
|
|
31
|
+
reject(new Error('Transaction goes wrong.'));
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
if (status && status.taskState === TaskState.ExecSuccess) {
|
|
35
|
+
resolve(status.transactionHash || '');
|
|
36
|
+
break;
|
|
37
|
+
} else if (
|
|
38
|
+
[
|
|
39
|
+
TaskState.Cancelled,
|
|
40
|
+
TaskState.ExecReverted,
|
|
41
|
+
TaskState.Blacklisted,
|
|
42
|
+
].includes(status?.taskState)
|
|
43
|
+
) {
|
|
44
|
+
reject(
|
|
45
|
+
new Error(
|
|
46
|
+
status.lastCheckMessage
|
|
47
|
+
?.split(/(RegisterDelegate)|(Execution error): /)
|
|
48
|
+
.at(-1) || ''
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await new Promise((r) => setTimeout(r, ttl));
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// gelato may throw 429 error, so we need to retry
|
|
58
|
+
// Increase ttl to avoid too deadlocking
|
|
59
|
+
// Max ttl is 30s
|
|
60
|
+
ttl += Math.max(30000, ttl + 1000);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
loop();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { watchGelatoTxn };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
export async function getIPFSData<T>(cid: string): Promise<T> {
|
|
4
|
+
try {
|
|
5
|
+
const { data } = await axios.get(`https://ipfs.io/ipfs/${cid}`, {
|
|
6
|
+
timeout: 5000,
|
|
7
|
+
});
|
|
8
|
+
return data as T;
|
|
9
|
+
} catch (err) {
|
|
10
|
+
console.error(err);
|
|
11
|
+
throw new Error(`Error to retrive data for CID: ${cid}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Networks } from "../consts";
|
|
2
|
+
import { ethers } from "ethers";
|
|
3
|
+
|
|
4
|
+
const providers: Record<number, ethers.JsonRpcProvider> = {};
|
|
5
|
+
|
|
6
|
+
export const getWeb3Provider = (chainId: number): ethers.JsonRpcProvider => {
|
|
7
|
+
const rpcUrl = Object.values(Networks).find((n) => n.chainId === chainId)
|
|
8
|
+
?.rpcUrl;
|
|
9
|
+
|
|
10
|
+
if (!rpcUrl) {
|
|
11
|
+
throw new Error(`No rpcUrl found for chainId ${chainId}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!providers[chainId]) {
|
|
15
|
+
providers[chainId] = new ethers.JsonRpcProvider(rpcUrl);
|
|
16
|
+
}
|
|
17
|
+
return providers[chainId];
|
|
18
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { nullRef } from "../consts";
|
|
2
|
+
import { Hex } from "../types";
|
|
3
|
+
|
|
4
|
+
const inStatement = (values: (string | number)[]) =>
|
|
5
|
+
`[${values.map((v) => `"${v}"`).join(",")}]`;
|
|
6
|
+
|
|
7
|
+
const attestationFields = `
|
|
8
|
+
uid: id
|
|
9
|
+
attester
|
|
10
|
+
data
|
|
11
|
+
decodedDataJson
|
|
12
|
+
recipient
|
|
13
|
+
revoked
|
|
14
|
+
createdAt: timeCreated
|
|
15
|
+
refUID
|
|
16
|
+
isOffchain
|
|
17
|
+
revocable
|
|
18
|
+
revocationTime
|
|
19
|
+
schemaId
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
const schemaQuery = (schemaId: Hex, content: string) =>
|
|
23
|
+
`{schema(where: {id: "${schemaId}"}) {${content}}}`;
|
|
24
|
+
|
|
25
|
+
export const gqlQueries = {
|
|
26
|
+
attestation: (uid: Hex) => `
|
|
27
|
+
{
|
|
28
|
+
attestation(where: {
|
|
29
|
+
id: "${uid}"
|
|
30
|
+
}) {${attestationFields}}
|
|
31
|
+
}`,
|
|
32
|
+
attestations: (schemaId: Hex, uid: Hex) =>
|
|
33
|
+
schemaQuery(
|
|
34
|
+
schemaId,
|
|
35
|
+
`attestations(where: {
|
|
36
|
+
revoked: {equals: false}
|
|
37
|
+
decodedDataJson: {contains: "${uid}"}
|
|
38
|
+
}) {${attestationFields}}`
|
|
39
|
+
),
|
|
40
|
+
attestationsIn: (uids: Hex[], search?: string) => `
|
|
41
|
+
{
|
|
42
|
+
attestations(where: {
|
|
43
|
+
id:{in: ${inStatement(uids)}}
|
|
44
|
+
revoked:{equals:false}
|
|
45
|
+
${
|
|
46
|
+
search
|
|
47
|
+
? `decodedDataJson:{contains:"${search}",mode:insensitive}`
|
|
48
|
+
: ""
|
|
49
|
+
}
|
|
50
|
+
}) {${attestationFields}}
|
|
51
|
+
}`,
|
|
52
|
+
attestationsFrom: (schemaId: Hex, attester: Hex) =>
|
|
53
|
+
schemaQuery(
|
|
54
|
+
schemaId,
|
|
55
|
+
`attestations(orderBy:{timeCreated: desc},
|
|
56
|
+
where:{attester:{equals:"${attester}"}
|
|
57
|
+
revoked:{equals:false}
|
|
58
|
+
}){${attestationFields}}`
|
|
59
|
+
),
|
|
60
|
+
attestationsTo: (schemaId: Hex, recipient: Hex) =>
|
|
61
|
+
schemaQuery(
|
|
62
|
+
schemaId,
|
|
63
|
+
`attestations(orderBy:{timeCreated: desc},
|
|
64
|
+
where:{
|
|
65
|
+
recipient:{equals:"${recipient}"}
|
|
66
|
+
revoked:{equals:false}
|
|
67
|
+
}){${attestationFields}}`
|
|
68
|
+
),
|
|
69
|
+
attestationPairs: (schemaId: Hex, attester: Hex, recipient: Hex) =>
|
|
70
|
+
schemaQuery(
|
|
71
|
+
schemaId,
|
|
72
|
+
`attestations(where: {
|
|
73
|
+
attester: {equals: "${attester}"}
|
|
74
|
+
recipient: {equals: "${recipient}"}
|
|
75
|
+
revoked: {equals: false}
|
|
76
|
+
}) {${attestationFields}}`
|
|
77
|
+
),
|
|
78
|
+
attestationsOf: (
|
|
79
|
+
schemaId: Hex,
|
|
80
|
+
search?: string[] | string,
|
|
81
|
+
refUids?: Hex[]
|
|
82
|
+
) =>
|
|
83
|
+
schemaQuery(
|
|
84
|
+
schemaId,
|
|
85
|
+
`attestations(orderBy:{timeCreated: desc},
|
|
86
|
+
where: {
|
|
87
|
+
revoked:{equals:false}
|
|
88
|
+
${
|
|
89
|
+
refUids && refUids.length
|
|
90
|
+
? `refUID:{in: ${inStatement(refUids)}}`
|
|
91
|
+
: ""
|
|
92
|
+
}
|
|
93
|
+
${
|
|
94
|
+
search
|
|
95
|
+
? `OR: [
|
|
96
|
+
${[search]
|
|
97
|
+
.flat()
|
|
98
|
+
.map(
|
|
99
|
+
(s) =>
|
|
100
|
+
`{decodedDataJson:{contains:"${s}",mode:insensitive}}`
|
|
101
|
+
)
|
|
102
|
+
.join(",")}
|
|
103
|
+
]`
|
|
104
|
+
: ""
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
{${attestationFields}}`
|
|
108
|
+
),
|
|
109
|
+
|
|
110
|
+
dependentsOf: (
|
|
111
|
+
refs: Hex | Hex[],
|
|
112
|
+
schemaIds: Hex[],
|
|
113
|
+
attesters: Hex[] = []
|
|
114
|
+
) => `
|
|
115
|
+
{
|
|
116
|
+
attestations(
|
|
117
|
+
orderBy:{timeCreated: desc},
|
|
118
|
+
where: {
|
|
119
|
+
refUID:{in: ${inStatement([refs].flat())}}
|
|
120
|
+
revoked:{equals: false}
|
|
121
|
+
schemaId:{in: ${inStatement(schemaIds)}}
|
|
122
|
+
${attesters.length ? `attester:{in:${inStatement(attesters)}}` : ""}
|
|
123
|
+
}){${attestationFields}}
|
|
124
|
+
}
|
|
125
|
+
`,
|
|
126
|
+
schemata: (creator: Hex) => `
|
|
127
|
+
{
|
|
128
|
+
schemata(where: {creator: {equals: "${creator}"}}) {
|
|
129
|
+
uid: id
|
|
130
|
+
schema
|
|
131
|
+
}
|
|
132
|
+
}`,
|
|
133
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filters an array by its condition then maps it to the desired format.
|
|
3
|
+
* @param arr
|
|
4
|
+
* @param condition
|
|
5
|
+
* @param mapTo
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
8
|
+
export function mapFilter<T = unknown, U = unknown>(
|
|
9
|
+
arr: U[],
|
|
10
|
+
condition: (item: U) => boolean,
|
|
11
|
+
mapTo: (item: U) => T
|
|
12
|
+
): T[] {
|
|
13
|
+
const newArray: T[] = [];
|
|
14
|
+
|
|
15
|
+
for (const item of arr) {
|
|
16
|
+
if (condition(item)) {
|
|
17
|
+
newArray.push(mapTo(item));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return newArray;
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function toUnix(value: number | Date | string) {
|
|
2
|
+
switch (typeof value) {
|
|
3
|
+
case "number":
|
|
4
|
+
value = Math.round(value);
|
|
5
|
+
if (value.toString().length > 13)
|
|
6
|
+
throw new Error("Invalid timestamp length");
|
|
7
|
+
if (value.toString().length === 10) return value;
|
|
8
|
+
return Math.floor(value / 1000);
|
|
9
|
+
case "string":
|
|
10
|
+
if (/\D/.test(value)) return null;
|
|
11
|
+
return toUnix(+value);
|
|
12
|
+
case "object":
|
|
13
|
+
if (value instanceof Date) return toUnix(value.getTime());
|
|
14
|
+
return null;
|
|
15
|
+
default:
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ethers } from "ethers";
|
|
2
|
+
import { GAP } from "./core/class/GAP";
|
|
3
|
+
import { GapIndexerClient } from "./core/class/karma-indexer/GapIndexerClient";
|
|
4
|
+
import { Community } from "./core/class/entities/Community";
|
|
5
|
+
import { ICommunityDetails } from "./core/class/types/attestations";
|
|
6
|
+
import { Hex } from "./core/types";
|
|
7
|
+
import * as dotenv from "dotenv";
|
|
8
|
+
|
|
9
|
+
// Load environment variables
|
|
10
|
+
dotenv.config();
|
|
11
|
+
|
|
12
|
+
// Configuration
|
|
13
|
+
const API_URL = process.env.API_URL || "https://gapstagapi.karmahq.xyz";
|
|
14
|
+
const RPC_URL = process.env.RPC_URL || "https://sepolia.optimism.io"; // Replace with your RPC URL
|
|
15
|
+
const PRIVATE_KEY = process.env.PRIVATE_KEY; // Your private key from .env file
|
|
16
|
+
|
|
17
|
+
if (!PRIVATE_KEY) {
|
|
18
|
+
throw new Error("PRIVATE_KEY not found in .env file");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Community data
|
|
22
|
+
const COMMUNITY_DATA = {
|
|
23
|
+
name: "Polygon",
|
|
24
|
+
description: "A polygon community",
|
|
25
|
+
imageURL:
|
|
26
|
+
"https://polygontechnology.notion.site/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F51562dc1-1dc5-4484-bf96-2aeac848ae2f%2Ff0ed090e-4957-4520-8b0c-56d5ec80a17f%2FPolygon_Icon_White_Purple_Rn-3.png?id=fdedfbfe-e6e1-430e-ac7c-b2fb988b47b9&table=block&spaceId=51562dc1-1dc5-4484-bf96-2aeac848ae2f&width=2000&userId=&cache=v2",
|
|
27
|
+
slug: "polygon", // Optional - will be auto-generated if not provided
|
|
28
|
+
links: [],
|
|
29
|
+
type: "community-details",
|
|
30
|
+
};
|
|
31
|
+
async function main() {
|
|
32
|
+
try {
|
|
33
|
+
console.log("=== Creating Community ===\n");
|
|
34
|
+
|
|
35
|
+
// Initialize provider and signer
|
|
36
|
+
const provider = new ethers.JsonRpcProvider(RPC_URL);
|
|
37
|
+
const signer = new ethers.Wallet(PRIVATE_KEY, provider);
|
|
38
|
+
const signerAddress = (await signer.getAddress()) as `0x${string}`;
|
|
39
|
+
|
|
40
|
+
console.log(`Using signer address: ${signerAddress}`);
|
|
41
|
+
|
|
42
|
+
// Initialize GAP SDK
|
|
43
|
+
const gap = new GAP({
|
|
44
|
+
globalSchemas: false,
|
|
45
|
+
network: "optimism-sepolia", // Change to your target network (e.g., "optimism", "arbitrum")
|
|
46
|
+
apiClient: new GapIndexerClient(API_URL),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
console.log("GAP SDK initialized\n");
|
|
50
|
+
|
|
51
|
+
// Generate a unique slug if not provided
|
|
52
|
+
let communityDetails: ICommunityDetails = { ...COMMUNITY_DATA };
|
|
53
|
+
if (!communityDetails.slug) {
|
|
54
|
+
console.log("Generating slug...");
|
|
55
|
+
communityDetails.slug = await gap.generateSlug(communityDetails.name);
|
|
56
|
+
console.log(`Generated slug: ${communityDetails.slug}\n`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Create the community attestation
|
|
60
|
+
console.log("Creating community attestation...");
|
|
61
|
+
const community = new Community({
|
|
62
|
+
schema: gap.findSchema("Community"),
|
|
63
|
+
recipient: signerAddress,
|
|
64
|
+
data: {
|
|
65
|
+
community: true,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Attest the community with its details
|
|
70
|
+
console.log("Attesting community to blockchain...\n");
|
|
71
|
+
const result = await community.attest(
|
|
72
|
+
signer,
|
|
73
|
+
communityDetails,
|
|
74
|
+
(status: string) => {
|
|
75
|
+
console.log(`Status: ${status}`);
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
console.log("\n✅ Community created successfully!");
|
|
80
|
+
console.log(`Community UID: ${result.uids[0]}`);
|
|
81
|
+
if (result.uids[1]) {
|
|
82
|
+
console.log(`Community Details UID: ${result.uids[1]}`);
|
|
83
|
+
}
|
|
84
|
+
console.log(`Transaction Hash: ${result.tx[0].hash}`);
|
|
85
|
+
console.log(
|
|
86
|
+
`\nYou can view your community at: https://gap.karmahq.xyz/community/${communityDetails.slug}`
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Fetch the created community to verify
|
|
90
|
+
console.log("\nVerifying community creation...");
|
|
91
|
+
const fetchedCommunity = await gap.fetch.communityBySlug(
|
|
92
|
+
communityDetails.slug
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (fetchedCommunity) {
|
|
96
|
+
console.log("✅ Community verified!");
|
|
97
|
+
console.log(`Community Name: ${fetchedCommunity.details?.name}`);
|
|
98
|
+
console.log(
|
|
99
|
+
`Community Description: ${fetchedCommunity.details?.description}`
|
|
100
|
+
);
|
|
101
|
+
} else {
|
|
102
|
+
console.log("⚠️ Community not yet indexed (this may take a few moments)");
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error("\n❌ Error creating community:", error);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Run the script
|
|
111
|
+
main()
|
|
112
|
+
.then(() => {
|
|
113
|
+
console.log("\n✅ Script completed successfully");
|
|
114
|
+
process.exit(0);
|
|
115
|
+
})
|
|
116
|
+
.catch((error) => {
|
|
117
|
+
console.error("\n❌ Script failed:", error);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
This directory contains configuration files for the application.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
### `config.ts`
|
|
8
|
+
|
|
9
|
+
Contains non-sensitive configuration and constants:
|
|
10
|
+
|
|
11
|
+
- `CHAIN_IDS`: Chain IDs for different networks
|
|
12
|
+
- `DEFAULT_CONFIG`: Default configuration values
|
|
13
|
+
- `API_ENDPOINTS`: API endpoint paths
|
|
14
|
+
- `LINK_TYPES`: Types of links supported
|
|
15
|
+
- `GRANT_UPDATE_TYPES`: Types of grant updates
|
|
16
|
+
|
|
17
|
+
### `keys.json`
|
|
18
|
+
|
|
19
|
+
Contains sensitive configuration (not committed to version control). Copy `keys.example.json` to `keys.json` and fill in your values.
|
|
20
|
+
|
|
21
|
+
Required structure:
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"optimism": {
|
|
25
|
+
"privateKey": "your_private_key_here",
|
|
26
|
+
"rpcURL": "your_rpc_url_here",
|
|
27
|
+
"gapAPI": "your_gap_api_url_here",
|
|
28
|
+
"ipfsKey": "your_ipfs_key_here"
|
|
29
|
+
},
|
|
30
|
+
// ... other networks with same structure
|
|
31
|
+
"gapAccessToken": "your_gap_access_token_here"
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Networks Supported
|
|
36
|
+
|
|
37
|
+
- optimism
|
|
38
|
+
- optimism-sepolia
|
|
39
|
+
- sepolia
|
|
40
|
+
- arbitrum
|
|
41
|
+
- base-sepolia
|
|
42
|
+
- celo
|
|
43
|
+
- sei
|
|
44
|
+
- sei-testnet
|
|
45
|
+
- lisk
|
|
46
|
+
- scroll
|
|
47
|
+
|
|
48
|
+
## Setup
|
|
49
|
+
|
|
50
|
+
1. Copy `keys.example.json` to `keys.json`
|
|
51
|
+
2. Fill in your values in `keys.json`
|
|
52
|
+
3. Never commit `keys.json` to version control
|
|
53
|
+
|
|
54
|
+
## Environment Variables
|
|
55
|
+
|
|
56
|
+
The following environment variables can override configuration values if present:
|
|
57
|
+
|
|
58
|
+
- `GAP_NETWORK`: Override the default network
|
|
59
|
+
- `GAP_API_URL`: Override the GAP API URL
|
|
60
|
+
- `MAINNET_RPC_URL`: Override the mainnet RPC URL
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
Import configuration values from `config.ts`:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import {
|
|
68
|
+
CHAIN_IDS,
|
|
69
|
+
DEFAULT_CONFIG,
|
|
70
|
+
API_ENDPOINTS,
|
|
71
|
+
LINK_TYPES,
|
|
72
|
+
GRANT_UPDATE_TYPES
|
|
73
|
+
} from "./config/config";
|
|
74
|
+
```
|