create-ponder 0.0.37 → 0.0.39
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/dist/{src/bin/create-ponder.d.ts → create-ponder.d.ts} +0 -1
- package/dist/create-ponder.js +862 -0
- package/dist/create-ponder.js.map +1 -0
- package/dist/create-ponder.mjs +839 -0
- package/dist/create-ponder.mjs.map +1 -0
- package/package.json +9 -8
- package/dist/src/bin/create-ponder.js +0 -103
- package/dist/src/common.d.ts +0 -22
- package/dist/src/common.js +0 -10
- package/dist/src/helpers/detectPackageManager.d.ts +0 -5
- package/dist/src/helpers/detectPackageManager.js +0 -78
- package/dist/src/helpers/getEtherscanChainId.d.ts +0 -5
- package/dist/src/helpers/getEtherscanChainId.js +0 -69
- package/dist/src/helpers/getGraphProtocolChainId.d.ts +0 -2
- package/dist/src/helpers/getGraphProtocolChainId.js +0 -41
- package/dist/src/helpers/git.d.ts +0 -1
- package/dist/src/helpers/git.js +0 -55
- package/dist/src/helpers/validateGraphProtocolSource.d.ts +0 -27
- package/dist/src/helpers/validateGraphProtocolSource.js +0 -7
- package/dist/src/helpers/wait.d.ts +0 -1
- package/dist/src/helpers/wait.js +0 -5
- package/dist/src/index.d.ts +0 -23
- package/dist/src/index.js +0 -154
- package/dist/src/templates/basic.d.ts +0 -4
- package/dist/src/templates/basic.js +0 -50
- package/dist/src/templates/etherscan.d.ts +0 -6
- package/dist/src/templates/etherscan.js +0 -112
- package/dist/src/templates/subgraphId.d.ts +0 -5
- package/dist/src/templates/subgraphId.js +0 -72
- package/dist/src/templates/subgraphRepo.d.ts +0 -5
- package/dist/src/templates/subgraphRepo.js +0 -80
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/bin/create-ponder.ts
|
|
4
|
+
import { cac } from "cac";
|
|
5
|
+
import path7 from "node:path";
|
|
6
|
+
import prompts from "prompts";
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
import { ethers } from "ethers";
|
|
10
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
11
|
+
import { mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
12
|
+
import path6 from "node:path";
|
|
13
|
+
import pico from "picocolors";
|
|
14
|
+
import prettier4 from "prettier";
|
|
15
|
+
|
|
16
|
+
// src/helpers/detectPackageManager.ts
|
|
17
|
+
import execa from "execa";
|
|
18
|
+
import { promises as fs } from "fs";
|
|
19
|
+
import { resolve } from "path";
|
|
20
|
+
async function pathExists(p) {
|
|
21
|
+
try {
|
|
22
|
+
await fs.access(p);
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
var cache = /* @__PURE__ */ new Map();
|
|
29
|
+
function hasGlobalInstallation(pm) {
|
|
30
|
+
const key = `has_global_${pm}`;
|
|
31
|
+
if (cache.has(key)) {
|
|
32
|
+
return Promise.resolve(cache.get(key));
|
|
33
|
+
}
|
|
34
|
+
return execa(pm, ["--version"]).then((res) => {
|
|
35
|
+
return /^\d+.\d+.\d+$/.test(res.stdout);
|
|
36
|
+
}).then((value) => {
|
|
37
|
+
cache.set(key, value);
|
|
38
|
+
return value;
|
|
39
|
+
}).catch(() => false);
|
|
40
|
+
}
|
|
41
|
+
function getTypeofLockFile(cwd = ".") {
|
|
42
|
+
const key = `lockfile_${cwd}`;
|
|
43
|
+
if (cache.has(key)) {
|
|
44
|
+
return Promise.resolve(cache.get(key));
|
|
45
|
+
}
|
|
46
|
+
return Promise.all([
|
|
47
|
+
pathExists(resolve(cwd, "yarn.lock")),
|
|
48
|
+
pathExists(resolve(cwd, "package-lock.json")),
|
|
49
|
+
pathExists(resolve(cwd, "pnpm-lock.yaml"))
|
|
50
|
+
]).then(([isYarn, isNpm, isPnpm]) => {
|
|
51
|
+
let value = null;
|
|
52
|
+
if (isYarn) {
|
|
53
|
+
value = "yarn";
|
|
54
|
+
} else if (isPnpm) {
|
|
55
|
+
value = "pnpm";
|
|
56
|
+
} else if (isNpm) {
|
|
57
|
+
value = "npm";
|
|
58
|
+
}
|
|
59
|
+
cache.set(key, value);
|
|
60
|
+
return value;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
var detect = async ({ cwd } = {}) => {
|
|
64
|
+
const type = await getTypeofLockFile(cwd);
|
|
65
|
+
if (type) {
|
|
66
|
+
return type;
|
|
67
|
+
}
|
|
68
|
+
const [hasYarn, hasPnpm] = await Promise.all([
|
|
69
|
+
hasGlobalInstallation("yarn"),
|
|
70
|
+
hasGlobalInstallation("pnpm")
|
|
71
|
+
]);
|
|
72
|
+
if (hasPnpm) {
|
|
73
|
+
return "pnpm";
|
|
74
|
+
}
|
|
75
|
+
if (hasYarn) {
|
|
76
|
+
return "yarn";
|
|
77
|
+
}
|
|
78
|
+
return "npm";
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// src/helpers/git.ts
|
|
82
|
+
import { execSync } from "child_process";
|
|
83
|
+
import path from "path";
|
|
84
|
+
import rimraf from "rimraf";
|
|
85
|
+
function isInGitRepository() {
|
|
86
|
+
try {
|
|
87
|
+
execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
|
|
88
|
+
return true;
|
|
89
|
+
} catch (_) {
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
function isInMercurialRepository() {
|
|
94
|
+
try {
|
|
95
|
+
execSync("hg --cwd . root", { stdio: "ignore" });
|
|
96
|
+
return true;
|
|
97
|
+
} catch (_) {
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
function tryGitInit(root) {
|
|
102
|
+
let didInit = false;
|
|
103
|
+
try {
|
|
104
|
+
execSync("git --version", { stdio: "ignore" });
|
|
105
|
+
if (isInGitRepository() || isInMercurialRepository()) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
execSync("git init", { stdio: "ignore" });
|
|
109
|
+
didInit = true;
|
|
110
|
+
execSync("git checkout -b main", { stdio: "ignore" });
|
|
111
|
+
execSync("git add -A", { stdio: "ignore" });
|
|
112
|
+
execSync('git commit -m "chore: initial commit from create-ponder"', {
|
|
113
|
+
stdio: "ignore"
|
|
114
|
+
});
|
|
115
|
+
return true;
|
|
116
|
+
} catch (e) {
|
|
117
|
+
if (didInit) {
|
|
118
|
+
try {
|
|
119
|
+
rimraf.sync(path.join(root, ".git"));
|
|
120
|
+
} catch (_) {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/templates/basic.ts
|
|
128
|
+
import { writeFileSync } from "node:fs";
|
|
129
|
+
import path2 from "node:path";
|
|
130
|
+
import prettier from "prettier";
|
|
131
|
+
var fromBasic = ({ rootDir }) => {
|
|
132
|
+
const abiFileContents = `[]`;
|
|
133
|
+
const abiRelativePath = "./abis/ExampleContract.json";
|
|
134
|
+
const abiAbsolutePath = path2.join(rootDir, abiRelativePath);
|
|
135
|
+
writeFileSync(abiAbsolutePath, abiFileContents);
|
|
136
|
+
const schemaGraphqlFileContents = `
|
|
137
|
+
type ExampleToken @entity {
|
|
138
|
+
id: ID!
|
|
139
|
+
tokenId: Int!
|
|
140
|
+
trait: TokenTrait!
|
|
141
|
+
}
|
|
142
|
+
enum TokenTrait {
|
|
143
|
+
GOOD
|
|
144
|
+
BAD
|
|
145
|
+
}
|
|
146
|
+
`;
|
|
147
|
+
const ponderSchemaFilePath = path2.join(rootDir, "schema.graphql");
|
|
148
|
+
writeFileSync(
|
|
149
|
+
ponderSchemaFilePath,
|
|
150
|
+
prettier.format(schemaGraphqlFileContents, { parser: "graphql" })
|
|
151
|
+
);
|
|
152
|
+
const ponderConfig = {
|
|
153
|
+
networks: [
|
|
154
|
+
{
|
|
155
|
+
name: "mainnet",
|
|
156
|
+
chainId: 1,
|
|
157
|
+
rpcUrl: `process.env.PONDER_RPC_URL_1`
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
contracts: [
|
|
161
|
+
{
|
|
162
|
+
name: "ExampleContract",
|
|
163
|
+
network: "mainnet",
|
|
164
|
+
address: "0x0",
|
|
165
|
+
abi: abiRelativePath,
|
|
166
|
+
startBlock: 1234567
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
};
|
|
170
|
+
return ponderConfig;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// src/templates/etherscan.ts
|
|
174
|
+
import { writeFileSync as writeFileSync2 } from "node:fs";
|
|
175
|
+
import path3 from "node:path";
|
|
176
|
+
import fetch from "node-fetch";
|
|
177
|
+
import prettier2 from "prettier";
|
|
178
|
+
|
|
179
|
+
// src/helpers/getEtherscanChainId.ts
|
|
180
|
+
var networkByEtherscanHostname = {
|
|
181
|
+
"etherscan.io": {
|
|
182
|
+
name: "mainnet",
|
|
183
|
+
chainId: 1,
|
|
184
|
+
apiUrl: "https://api.etherscan.io/api"
|
|
185
|
+
},
|
|
186
|
+
"ropsten.etherscan.io": {
|
|
187
|
+
name: "ropsten",
|
|
188
|
+
chainId: 3,
|
|
189
|
+
apiUrl: "https://api-ropsten.etherscan.io/api"
|
|
190
|
+
},
|
|
191
|
+
"rinkeby.etherscan.io": {
|
|
192
|
+
name: "rinkeby",
|
|
193
|
+
chainId: 4,
|
|
194
|
+
apiUrl: "https://api-rinkeby.etherscan.io/api"
|
|
195
|
+
},
|
|
196
|
+
"goerli.etherscan.io": {
|
|
197
|
+
name: "goerli",
|
|
198
|
+
chainId: 5,
|
|
199
|
+
apiUrl: "https://api-goerli.etherscan.io/api"
|
|
200
|
+
},
|
|
201
|
+
"kovan.etherscan.io": {
|
|
202
|
+
name: "kovan",
|
|
203
|
+
chainId: 42,
|
|
204
|
+
apiUrl: "https://api-kovan.etherscan.io/api"
|
|
205
|
+
},
|
|
206
|
+
"sepolia.etherscan.io": {
|
|
207
|
+
name: "sepolia",
|
|
208
|
+
chainId: 11155111,
|
|
209
|
+
apiUrl: "https://api-sepolia.etherscan.io/api"
|
|
210
|
+
},
|
|
211
|
+
"optimistic.etherscan.io": {
|
|
212
|
+
name: "optimism",
|
|
213
|
+
chainId: 10,
|
|
214
|
+
apiUrl: "https://api-optimistic.etherscan.io/api"
|
|
215
|
+
},
|
|
216
|
+
"goerli-optimism.etherscan.io": {
|
|
217
|
+
name: "optimism-goerli",
|
|
218
|
+
chainId: 420,
|
|
219
|
+
apiUrl: "https://api-goerli-optimistic.etherscan.io/api"
|
|
220
|
+
},
|
|
221
|
+
"polygonscan.com": {
|
|
222
|
+
name: "polygon",
|
|
223
|
+
chainId: 137,
|
|
224
|
+
apiUrl: "https://api.polygonscan.com/api"
|
|
225
|
+
},
|
|
226
|
+
"mumbai.polygonscan.com": {
|
|
227
|
+
name: "polygon-mumbai",
|
|
228
|
+
chainId: 80001,
|
|
229
|
+
apiUrl: "https://api-testnet.polygonscan.com/api"
|
|
230
|
+
},
|
|
231
|
+
"arbiscan.io": {
|
|
232
|
+
name: "arbitrum",
|
|
233
|
+
chainId: 42161,
|
|
234
|
+
apiUrl: "https://api.arbiscan.io/api"
|
|
235
|
+
},
|
|
236
|
+
"goerli.arbiscan.io": {
|
|
237
|
+
name: "arbitrum-goerli",
|
|
238
|
+
chainId: 421613,
|
|
239
|
+
apiUrl: "https://api-goerli.arbiscan.io/api"
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
var getNetworkByEtherscanHostname = (hostname) => {
|
|
243
|
+
return networkByEtherscanHostname[hostname];
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// src/helpers/wait.ts
|
|
247
|
+
var wait = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
248
|
+
|
|
249
|
+
// src/templates/etherscan.ts
|
|
250
|
+
var fromEtherscan = async ({
|
|
251
|
+
rootDir,
|
|
252
|
+
etherscanLink,
|
|
253
|
+
etherscanApiKey
|
|
254
|
+
}) => {
|
|
255
|
+
const apiKey = etherscanApiKey || process.env.ETHERSCAN_API_KEY;
|
|
256
|
+
const url = new URL(etherscanLink);
|
|
257
|
+
const network = getNetworkByEtherscanHostname(url.hostname);
|
|
258
|
+
if (!network) {
|
|
259
|
+
throw new Error(`Unrecognized etherscan hostname: ${url.hostname}`);
|
|
260
|
+
}
|
|
261
|
+
const { name, chainId, apiUrl } = network;
|
|
262
|
+
const contractAddress = url.pathname.slice(1).split("/")[1];
|
|
263
|
+
const txHash = await getContractCreationTxn(contractAddress, apiUrl, apiKey);
|
|
264
|
+
if (!apiKey) {
|
|
265
|
+
console.log("\n(1/2) Waiting 5 seconds for Etherscan API rate limit");
|
|
266
|
+
await wait(5e3);
|
|
267
|
+
}
|
|
268
|
+
const blockNumber = await getTxBlockNumber(txHash, apiUrl, apiKey);
|
|
269
|
+
if (!apiKey) {
|
|
270
|
+
console.log("(2/2) Waiting 5 seconds for Etherscan API rate limit");
|
|
271
|
+
await wait(5e3);
|
|
272
|
+
}
|
|
273
|
+
const { abi, contractName } = await getContractAbiAndName(
|
|
274
|
+
contractAddress,
|
|
275
|
+
apiUrl,
|
|
276
|
+
apiKey
|
|
277
|
+
);
|
|
278
|
+
const abiRelativePath = `./abis/${contractName}.json`;
|
|
279
|
+
const abiAbsolutePath = path3.join(rootDir, abiRelativePath);
|
|
280
|
+
writeFileSync2(abiAbsolutePath, prettier2.format(abi, { parser: "json" }));
|
|
281
|
+
const schemaGraphqlFileContents = `
|
|
282
|
+
type ExampleEntity @entity {
|
|
283
|
+
id: ID!
|
|
284
|
+
name: String!
|
|
285
|
+
}
|
|
286
|
+
`;
|
|
287
|
+
const ponderSchemaFilePath = path3.join(rootDir, "schema.graphql");
|
|
288
|
+
writeFileSync2(
|
|
289
|
+
ponderSchemaFilePath,
|
|
290
|
+
prettier2.format(schemaGraphqlFileContents, { parser: "graphql" })
|
|
291
|
+
);
|
|
292
|
+
const ponderConfig = {
|
|
293
|
+
networks: [
|
|
294
|
+
{
|
|
295
|
+
name,
|
|
296
|
+
chainId,
|
|
297
|
+
rpcUrl: `process.env.PONDER_RPC_URL_${chainId}`
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
contracts: [
|
|
301
|
+
{
|
|
302
|
+
name: contractName,
|
|
303
|
+
network: name,
|
|
304
|
+
abi: abiRelativePath,
|
|
305
|
+
address: contractAddress,
|
|
306
|
+
startBlock: blockNumber
|
|
307
|
+
}
|
|
308
|
+
]
|
|
309
|
+
};
|
|
310
|
+
return ponderConfig;
|
|
311
|
+
};
|
|
312
|
+
var fetchEtherscan = async (url) => {
|
|
313
|
+
const response = await fetch(url);
|
|
314
|
+
const data = await response.json();
|
|
315
|
+
if (data.status === "0") {
|
|
316
|
+
throw new Error(`Etherscan API error: ${data.result}`);
|
|
317
|
+
}
|
|
318
|
+
return data;
|
|
319
|
+
};
|
|
320
|
+
var getContractCreationTxn = async (contractAddress, apiUrl, apiKey) => {
|
|
321
|
+
const searchParams = new URLSearchParams({
|
|
322
|
+
module: "contract",
|
|
323
|
+
action: "getcontractcreation",
|
|
324
|
+
contractaddresses: contractAddress
|
|
325
|
+
});
|
|
326
|
+
if (apiKey)
|
|
327
|
+
searchParams.append("apikey", apiKey);
|
|
328
|
+
const data = await fetchEtherscan(`${apiUrl}?${searchParams.toString()}`);
|
|
329
|
+
return data.result[0].txHash;
|
|
330
|
+
};
|
|
331
|
+
var getTxBlockNumber = async (txHash, apiUrl, apiKey) => {
|
|
332
|
+
const searchParams = new URLSearchParams({
|
|
333
|
+
module: "proxy",
|
|
334
|
+
action: "eth_getTransactionByHash",
|
|
335
|
+
txhash: txHash
|
|
336
|
+
});
|
|
337
|
+
if (apiKey)
|
|
338
|
+
searchParams.append("apikey", apiKey);
|
|
339
|
+
const data = await fetchEtherscan(`${apiUrl}?${searchParams.toString()}`);
|
|
340
|
+
const hexBlockNumber = data.result.blockNumber;
|
|
341
|
+
return parseInt(hexBlockNumber.slice(2), 16);
|
|
342
|
+
};
|
|
343
|
+
var getContractAbiAndName = async (contractAddress, apiUrl, apiKey) => {
|
|
344
|
+
const searchParams = new URLSearchParams({
|
|
345
|
+
module: "contract",
|
|
346
|
+
action: "getsourcecode",
|
|
347
|
+
address: contractAddress
|
|
348
|
+
});
|
|
349
|
+
if (apiKey)
|
|
350
|
+
searchParams.append("apikey", apiKey);
|
|
351
|
+
const data = await fetchEtherscan(`${apiUrl}?${searchParams.toString()}`);
|
|
352
|
+
const abi = data.result[0].ABI;
|
|
353
|
+
const contractName = data.result[0].ContractName;
|
|
354
|
+
return { abi, contractName };
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// src/templates/subgraphId.ts
|
|
358
|
+
import { writeFileSync as writeFileSync3 } from "node:fs";
|
|
359
|
+
import path4 from "node:path";
|
|
360
|
+
import fetch2 from "node-fetch";
|
|
361
|
+
import prettier3 from "prettier";
|
|
362
|
+
import { parse } from "yaml";
|
|
363
|
+
|
|
364
|
+
// src/helpers/getGraphProtocolChainId.ts
|
|
365
|
+
var chainIdByGraphNetwork = {
|
|
366
|
+
mainnet: 1,
|
|
367
|
+
kovan: 42,
|
|
368
|
+
rinkeby: 4,
|
|
369
|
+
ropsten: 3,
|
|
370
|
+
goerli: 5,
|
|
371
|
+
"poa-core": 99,
|
|
372
|
+
"poa-sokol": 77,
|
|
373
|
+
xdai: 100,
|
|
374
|
+
matic: 137,
|
|
375
|
+
mumbai: 80001,
|
|
376
|
+
fantom: 250,
|
|
377
|
+
"fantom-testnet": 4002,
|
|
378
|
+
bsc: 56,
|
|
379
|
+
chapel: -1,
|
|
380
|
+
clover: 0,
|
|
381
|
+
avalanche: 43114,
|
|
382
|
+
fuji: 43113,
|
|
383
|
+
celo: 42220,
|
|
384
|
+
"celo-alfajores": 44787,
|
|
385
|
+
fuse: 122,
|
|
386
|
+
moonbeam: 1284,
|
|
387
|
+
moonriver: 1285,
|
|
388
|
+
mbase: -1,
|
|
389
|
+
"arbitrum-one": 42161,
|
|
390
|
+
"arbitrum-rinkeby": 421611,
|
|
391
|
+
optimism: 10,
|
|
392
|
+
"optimism-kovan": 69,
|
|
393
|
+
aurora: 1313161554,
|
|
394
|
+
"aurora-testnet": 1313161555
|
|
395
|
+
};
|
|
396
|
+
var getGraphProtocolChainId = (networkName) => {
|
|
397
|
+
return chainIdByGraphNetwork[networkName];
|
|
398
|
+
};
|
|
399
|
+
var subgraphYamlFileNames = ["subgraph.yaml"].concat(
|
|
400
|
+
Object.keys(chainIdByGraphNetwork).map((n) => `subgraph-${n}.yaml`)
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
// src/helpers/validateGraphProtocolSource.ts
|
|
404
|
+
var validateGraphProtocolSource = (source) => {
|
|
405
|
+
return source;
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// src/templates/subgraphId.ts
|
|
409
|
+
var fetchIpfsFile = async (cid) => {
|
|
410
|
+
const url = `https://ipfs.network.thegraph.com/api/v0/cat?arg=${cid}`;
|
|
411
|
+
const response = await fetch2(url);
|
|
412
|
+
const contentRaw = await response.text();
|
|
413
|
+
return contentRaw;
|
|
414
|
+
};
|
|
415
|
+
var fromSubgraphId = async ({
|
|
416
|
+
rootDir,
|
|
417
|
+
subgraphId
|
|
418
|
+
}) => {
|
|
419
|
+
const ponderNetworks = [];
|
|
420
|
+
let ponderContracts = [];
|
|
421
|
+
const manifestRaw = await fetchIpfsFile(subgraphId);
|
|
422
|
+
const manifest = parse(manifestRaw);
|
|
423
|
+
const schemaCid = manifest.schema.file["/"].slice(6);
|
|
424
|
+
const schemaRaw = await fetchIpfsFile(schemaCid);
|
|
425
|
+
const ponderSchemaFilePath = path4.join(rootDir, "schema.graphql");
|
|
426
|
+
writeFileSync3(
|
|
427
|
+
ponderSchemaFilePath,
|
|
428
|
+
prettier3.format(schemaRaw, { parser: "graphql" })
|
|
429
|
+
);
|
|
430
|
+
const dataSources = manifest.dataSources.map(
|
|
431
|
+
validateGraphProtocolSource
|
|
432
|
+
);
|
|
433
|
+
const abiFiles = dataSources.map((source) => source.mapping.abis).flat().filter(
|
|
434
|
+
(source, idx, arr) => arr.findIndex((s) => s.name === source.name) === idx
|
|
435
|
+
);
|
|
436
|
+
await Promise.all(
|
|
437
|
+
abiFiles.map(async (abi) => {
|
|
438
|
+
const abiContent = await fetchIpfsFile(abi.file["/"].slice(6));
|
|
439
|
+
const abiPath = path4.join(rootDir, `./abis/${abi.name}.json`);
|
|
440
|
+
writeFileSync3(abiPath, prettier3.format(abiContent, { parser: "json" }));
|
|
441
|
+
})
|
|
442
|
+
);
|
|
443
|
+
ponderContracts = dataSources.map((source) => {
|
|
444
|
+
const network = source.network || "mainnet";
|
|
445
|
+
const chainId = getGraphProtocolChainId(network);
|
|
446
|
+
if (!chainId || chainId === -1) {
|
|
447
|
+
throw new Error(`Unhandled network name: ${network}`);
|
|
448
|
+
}
|
|
449
|
+
if (!ponderNetworks.map((n) => n.name).includes(network)) {
|
|
450
|
+
ponderNetworks.push({
|
|
451
|
+
name: network,
|
|
452
|
+
chainId,
|
|
453
|
+
rpcUrl: `process.env.PONDER_RPC_URL_${chainId}`
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
const abiRelativePath = `./abis/${source.source.abi}.json`;
|
|
457
|
+
return {
|
|
458
|
+
name: source.name,
|
|
459
|
+
network,
|
|
460
|
+
address: source.source.address,
|
|
461
|
+
abi: abiRelativePath,
|
|
462
|
+
startBlock: source.source.startBlock
|
|
463
|
+
};
|
|
464
|
+
});
|
|
465
|
+
const ponderConfig = {
|
|
466
|
+
networks: ponderNetworks,
|
|
467
|
+
contracts: ponderContracts
|
|
468
|
+
};
|
|
469
|
+
return ponderConfig;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// src/templates/subgraphRepo.ts
|
|
473
|
+
import { copyFileSync, readFileSync } from "node:fs";
|
|
474
|
+
import path5 from "node:path";
|
|
475
|
+
import { parse as parse2 } from "yaml";
|
|
476
|
+
var fromSubgraphRepo = ({
|
|
477
|
+
rootDir,
|
|
478
|
+
subgraphPath
|
|
479
|
+
}) => {
|
|
480
|
+
const subgraphRootDir = path5.resolve(subgraphPath);
|
|
481
|
+
const ponderNetworks = [];
|
|
482
|
+
let ponderContracts = [];
|
|
483
|
+
const subgraphRootDirPath = path5.resolve(subgraphRootDir);
|
|
484
|
+
let subgraphYamlRaw = "";
|
|
485
|
+
for (const subgraphYamlFileName of subgraphYamlFileNames) {
|
|
486
|
+
try {
|
|
487
|
+
subgraphYamlRaw = readFileSync(
|
|
488
|
+
path5.join(subgraphRootDirPath, subgraphYamlFileName),
|
|
489
|
+
{
|
|
490
|
+
encoding: "utf-8"
|
|
491
|
+
}
|
|
492
|
+
);
|
|
493
|
+
break;
|
|
494
|
+
} catch (e) {
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (subgraphYamlRaw === "") {
|
|
499
|
+
throw new Error(`subgraph.yaml file not found`);
|
|
500
|
+
}
|
|
501
|
+
const subgraphYaml = parse2(subgraphYamlRaw);
|
|
502
|
+
const subgraphSchemaFilePath = path5.join(
|
|
503
|
+
subgraphRootDirPath,
|
|
504
|
+
subgraphYaml.schema.file
|
|
505
|
+
);
|
|
506
|
+
const ponderSchemaFilePath = path5.join(rootDir, "schema.graphql");
|
|
507
|
+
copyFileSync(subgraphSchemaFilePath, ponderSchemaFilePath);
|
|
508
|
+
ponderContracts = subgraphYaml.dataSources.map(validateGraphProtocolSource).map((source) => {
|
|
509
|
+
const abiPath = source.mapping.abis.find(
|
|
510
|
+
(abi) => abi.name === source.name
|
|
511
|
+
)?.file;
|
|
512
|
+
if (!abiPath) {
|
|
513
|
+
throw new Error(`ABI path not found for source: ${source.name}`);
|
|
514
|
+
}
|
|
515
|
+
const network = source.network || "mainnet";
|
|
516
|
+
const chainId = getGraphProtocolChainId(network);
|
|
517
|
+
if (!chainId || chainId === -1) {
|
|
518
|
+
throw new Error(`Unhandled network name: ${network}`);
|
|
519
|
+
}
|
|
520
|
+
if (!ponderNetworks.map((n) => n.name).includes(network)) {
|
|
521
|
+
ponderNetworks.push({
|
|
522
|
+
name: network,
|
|
523
|
+
chainId,
|
|
524
|
+
rpcUrl: `process.env.PONDER_RPC_URL_${chainId}`
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
const abiAbsolutePath = path5.join(subgraphRootDirPath, abiPath);
|
|
528
|
+
const abiFileName = path5.basename(abiPath);
|
|
529
|
+
const ponderAbiRelativePath = `./abis/${abiFileName}`;
|
|
530
|
+
const ponderAbiAbsolutePath = path5.join(rootDir, ponderAbiRelativePath);
|
|
531
|
+
copyFileSync(abiAbsolutePath, ponderAbiAbsolutePath);
|
|
532
|
+
return {
|
|
533
|
+
name: source.name,
|
|
534
|
+
network,
|
|
535
|
+
address: source.source.address,
|
|
536
|
+
abi: ponderAbiRelativePath,
|
|
537
|
+
startBlock: source.source.startBlock
|
|
538
|
+
};
|
|
539
|
+
});
|
|
540
|
+
const ponderConfig = {
|
|
541
|
+
networks: ponderNetworks,
|
|
542
|
+
contracts: ponderContracts
|
|
543
|
+
};
|
|
544
|
+
return ponderConfig;
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
// src/index.ts
|
|
548
|
+
var run = async (options, overrides = {}) => {
|
|
549
|
+
const { rootDir } = options;
|
|
550
|
+
mkdirSync(path6.join(rootDir, "abis"), { recursive: true });
|
|
551
|
+
mkdirSync(path6.join(rootDir, "src"), { recursive: true });
|
|
552
|
+
let ponderConfig;
|
|
553
|
+
console.log(
|
|
554
|
+
`
|
|
555
|
+
Creating a new Ponder app in ${pico.bold(pico.green(rootDir))}.`
|
|
556
|
+
);
|
|
557
|
+
switch (options.template?.kind) {
|
|
558
|
+
case 1 /* ETHERSCAN */: {
|
|
559
|
+
console.log(`
|
|
560
|
+
Using ${pico.cyan("Etherscan contract link")} template.`);
|
|
561
|
+
ponderConfig = await fromEtherscan({
|
|
562
|
+
rootDir,
|
|
563
|
+
etherscanLink: options.template.link,
|
|
564
|
+
etherscanApiKey: options.etherscanApiKey
|
|
565
|
+
});
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
case 2 /* SUBGRAPH_ID */: {
|
|
569
|
+
console.log(`
|
|
570
|
+
Using ${pico.cyan("Subgraph ID")} template.`);
|
|
571
|
+
ponderConfig = await fromSubgraphId({
|
|
572
|
+
rootDir,
|
|
573
|
+
subgraphId: options.template.id
|
|
574
|
+
});
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
case 3 /* SUBGRAPH_REPO */: {
|
|
578
|
+
console.log(`
|
|
579
|
+
Using ${pico.cyan("Subgraph repository")} template.`);
|
|
580
|
+
ponderConfig = fromSubgraphRepo({
|
|
581
|
+
rootDir,
|
|
582
|
+
subgraphPath: options.template.path
|
|
583
|
+
});
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
default: {
|
|
587
|
+
ponderConfig = fromBasic({ rootDir });
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
ponderConfig.contracts.forEach((contract) => {
|
|
592
|
+
const abi = readFileSync2(path6.join(rootDir, contract.abi), {
|
|
593
|
+
encoding: "utf-8"
|
|
594
|
+
});
|
|
595
|
+
const abiInterface = new ethers.utils.Interface(abi);
|
|
596
|
+
const eventNames = Object.keys(abiInterface.events).map(
|
|
597
|
+
(signature) => signature.slice(0, signature.indexOf("("))
|
|
598
|
+
);
|
|
599
|
+
const eventNamesToWrite = eventNames.slice(0, 2);
|
|
600
|
+
const handlerFileContents = `
|
|
601
|
+
import { ponder } from '../generated'
|
|
602
|
+
|
|
603
|
+
${eventNamesToWrite.map(
|
|
604
|
+
(eventName) => `
|
|
605
|
+
ponder.on("${contract.name}:${eventName}", async ({ event, context }) => {
|
|
606
|
+
console.log(event.params)
|
|
607
|
+
})`
|
|
608
|
+
).join("\n")}
|
|
609
|
+
`;
|
|
610
|
+
writeFileSync4(
|
|
611
|
+
path6.join(rootDir, `./src/${contract.name}.ts`),
|
|
612
|
+
prettier4.format(handlerFileContents, { parser: "typescript" })
|
|
613
|
+
);
|
|
614
|
+
});
|
|
615
|
+
const finalPonderConfig = `
|
|
616
|
+
import type { PonderConfig } from "@ponder/core";
|
|
617
|
+
|
|
618
|
+
export const config: PonderConfig = {
|
|
619
|
+
networks: ${JSON.stringify(ponderConfig.networks).replaceAll(
|
|
620
|
+
/"process.env.PONDER_RPC_URL_(.*?)"/g,
|
|
621
|
+
"process.env.PONDER_RPC_URL_$1"
|
|
622
|
+
)},
|
|
623
|
+
contracts: ${JSON.stringify(ponderConfig.contracts)},
|
|
624
|
+
};
|
|
625
|
+
`;
|
|
626
|
+
writeFileSync4(
|
|
627
|
+
path6.join(rootDir, "ponder.config.ts"),
|
|
628
|
+
prettier4.format(finalPonderConfig, { parser: "babel" })
|
|
629
|
+
);
|
|
630
|
+
const uniqueChainIds = Array.from(
|
|
631
|
+
new Set(ponderConfig.networks.map((n) => n.chainId))
|
|
632
|
+
);
|
|
633
|
+
const envLocal = `${uniqueChainIds.map(
|
|
634
|
+
(chainId) => `PONDER_RPC_URL_${chainId}=""
|
|
635
|
+
`
|
|
636
|
+
)}`;
|
|
637
|
+
writeFileSync4(path6.join(rootDir, ".env.local"), envLocal);
|
|
638
|
+
const packageJson = `
|
|
639
|
+
{
|
|
640
|
+
"private": true,
|
|
641
|
+
"scripts": {
|
|
642
|
+
"dev": "ponder dev",
|
|
643
|
+
"start": "ponder start",
|
|
644
|
+
"codegen": "ponder codegen"
|
|
645
|
+
},
|
|
646
|
+
"dependencies": {
|
|
647
|
+
"@ponder/core": "latest"
|
|
648
|
+
},
|
|
649
|
+
"devDependencies": {
|
|
650
|
+
"@types/node": "^18.11.18",
|
|
651
|
+
"ethers": "^5.6.9"
|
|
652
|
+
},
|
|
653
|
+
"engines": {
|
|
654
|
+
"node": ">=16.0.0 <19.0.0"
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
`;
|
|
658
|
+
writeFileSync4(
|
|
659
|
+
path6.join(rootDir, "package.json"),
|
|
660
|
+
prettier4.format(packageJson, { parser: "json" })
|
|
661
|
+
);
|
|
662
|
+
const tsConfig = `
|
|
663
|
+
{
|
|
664
|
+
"compilerOptions": {
|
|
665
|
+
"target": "esnext",
|
|
666
|
+
"module": "esnext",
|
|
667
|
+
"esModuleInterop": true,
|
|
668
|
+
"strict": true,
|
|
669
|
+
"moduleResolution": "node"
|
|
670
|
+
},
|
|
671
|
+
"include": ["./**/*.ts"],
|
|
672
|
+
"exclude": ["node_modules"]
|
|
673
|
+
}
|
|
674
|
+
`;
|
|
675
|
+
writeFileSync4(
|
|
676
|
+
path6.join(rootDir, "tsconfig.json"),
|
|
677
|
+
prettier4.format(tsConfig, { parser: "json" })
|
|
678
|
+
);
|
|
679
|
+
writeFileSync4(
|
|
680
|
+
path6.join(rootDir, ".gitignore"),
|
|
681
|
+
`node_modules/
|
|
682
|
+
.DS_Store
|
|
683
|
+
|
|
684
|
+
.env.local
|
|
685
|
+
.ponder/
|
|
686
|
+
generated/`
|
|
687
|
+
);
|
|
688
|
+
const packageManager = await detect();
|
|
689
|
+
const runCommand = packageManager === "npm" ? `${packageManager} run` : packageManager;
|
|
690
|
+
console.log(pico.bold(`
|
|
691
|
+
Installing with ${packageManager}.`));
|
|
692
|
+
const installCommand = overrides.installCommand ? overrides.installCommand : `${packageManager} install`;
|
|
693
|
+
execSync2(installCommand, {
|
|
694
|
+
cwd: rootDir,
|
|
695
|
+
stdio: "inherit"
|
|
696
|
+
});
|
|
697
|
+
process.chdir(rootDir);
|
|
698
|
+
tryGitInit(rootDir);
|
|
699
|
+
console.log(`
|
|
700
|
+
Initialized a git repository.`);
|
|
701
|
+
execSync2(`${runCommand} --silent codegen --silent`, {
|
|
702
|
+
cwd: rootDir,
|
|
703
|
+
stdio: "inherit"
|
|
704
|
+
});
|
|
705
|
+
console.log(`
|
|
706
|
+
Generated types.`);
|
|
707
|
+
console.log(
|
|
708
|
+
pico.green("\nSuccess! ") + `Created ${options.projectName} at ${rootDir}`
|
|
709
|
+
);
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// package.json
|
|
713
|
+
var package_default = {
|
|
714
|
+
name: "create-ponder",
|
|
715
|
+
version: "0.0.39",
|
|
716
|
+
description: "Tool to bootstrap a Ponder project",
|
|
717
|
+
license: "MIT",
|
|
718
|
+
author: "olias.eth",
|
|
719
|
+
files: [
|
|
720
|
+
"dist"
|
|
721
|
+
],
|
|
722
|
+
bin: {
|
|
723
|
+
"create-ponder": "dist/create-ponder.js"
|
|
724
|
+
},
|
|
725
|
+
scripts: {
|
|
726
|
+
build: "tsup-node",
|
|
727
|
+
test: "export $(grep -v '^#' .env.local | xargs) && vitest --no-threads",
|
|
728
|
+
"test:ci": "vitest --no-threads",
|
|
729
|
+
typecheck: "tsc --noEmit"
|
|
730
|
+
},
|
|
731
|
+
dependencies: {
|
|
732
|
+
"@ethersproject/abi": "^5.6.4",
|
|
733
|
+
"@ethersproject/providers": "^5.6.8",
|
|
734
|
+
cac: "^6.7.14",
|
|
735
|
+
ethers: "^5.6.9",
|
|
736
|
+
execa: "5",
|
|
737
|
+
"node-fetch": "^2.6.7",
|
|
738
|
+
picocolors: "^1.0.0",
|
|
739
|
+
prettier: "^2.6.2",
|
|
740
|
+
prompts: "^2.4.2",
|
|
741
|
+
rimraf: "^4.1.2",
|
|
742
|
+
yaml: "^2.1.1"
|
|
743
|
+
},
|
|
744
|
+
devDependencies: {
|
|
745
|
+
"@ponder/core": "workspace:*",
|
|
746
|
+
"@types/node": "^18.7.8",
|
|
747
|
+
"@types/node-fetch": "2",
|
|
748
|
+
"@types/prettier": "^2.7.1",
|
|
749
|
+
"@types/prompts": "^2.4.2",
|
|
750
|
+
"tsconfig-replace-paths": "^0.0.11",
|
|
751
|
+
tsup: "^6.6.3",
|
|
752
|
+
typescript: "^4.5.5",
|
|
753
|
+
vitest: "^0.29.2"
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
// src/bin/create-ponder.ts
|
|
758
|
+
var createPonder = async () => {
|
|
759
|
+
const cli = cac(package_default.name).version(package_default.version).usage("[options]").help().option("--dir [path]", "Path to directory for generated project").option("--from-subgraph-id [id]", "Subgraph deployment ID").option("--from-subgraph-repo [path]", "Path to subgraph repository").option("--from-etherscan [url]", "Link to etherscan contract page").option("--etherscan-api-key [key]", "Etherscan API key");
|
|
760
|
+
const parsed = cli.parse(process.argv);
|
|
761
|
+
const options = parsed.options;
|
|
762
|
+
if (options.help) {
|
|
763
|
+
process.exit(0);
|
|
764
|
+
}
|
|
765
|
+
const { fromEtherscan: fromEtherscan2, fromSubgraphId: fromSubgraphId2, fromSubgraphRepo: fromSubgraphRepo2 } = options;
|
|
766
|
+
if (fromSubgraphId2 && fromSubgraphRepo2 || fromSubgraphId2 && fromEtherscan2 || fromSubgraphRepo2 && fromEtherscan2) {
|
|
767
|
+
throw new Error(
|
|
768
|
+
`Cannot specify more than one "--from" option:
|
|
769
|
+
--from-subgraph
|
|
770
|
+
--from-etherscan-id
|
|
771
|
+
--from-etherscan-repo`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
const { projectName } = await prompts({
|
|
775
|
+
type: "text",
|
|
776
|
+
name: "projectName",
|
|
777
|
+
message: "What is your project named?",
|
|
778
|
+
initial: "my-app"
|
|
779
|
+
});
|
|
780
|
+
let template = void 0;
|
|
781
|
+
if (fromEtherscan2) {
|
|
782
|
+
template = { kind: 1 /* ETHERSCAN */, link: fromEtherscan2 };
|
|
783
|
+
}
|
|
784
|
+
if (fromSubgraphId2) {
|
|
785
|
+
template = { kind: 2 /* SUBGRAPH_ID */, id: fromSubgraphId2 };
|
|
786
|
+
}
|
|
787
|
+
if (fromSubgraphRepo2) {
|
|
788
|
+
template = { kind: 3 /* SUBGRAPH_REPO */, path: fromSubgraphRepo2 };
|
|
789
|
+
}
|
|
790
|
+
if (!fromSubgraphId2 && !fromSubgraphRepo2 && !fromEtherscan2) {
|
|
791
|
+
const { template: templateKind } = await prompts({
|
|
792
|
+
type: "select",
|
|
793
|
+
name: "template",
|
|
794
|
+
message: "Would you like to use a template for this project?",
|
|
795
|
+
choices: [
|
|
796
|
+
{ title: "None" },
|
|
797
|
+
{ title: "Etherscan contract link" },
|
|
798
|
+
{ title: "Subgraph ID" },
|
|
799
|
+
{ title: "Subgraph repository" }
|
|
800
|
+
]
|
|
801
|
+
});
|
|
802
|
+
if (templateKind === 1 /* ETHERSCAN */) {
|
|
803
|
+
const { link } = await prompts({
|
|
804
|
+
type: "text",
|
|
805
|
+
name: "link",
|
|
806
|
+
message: "Enter an Etherscan contract link",
|
|
807
|
+
initial: "https://etherscan.io/address/0x97..."
|
|
808
|
+
});
|
|
809
|
+
template = { kind: 1 /* ETHERSCAN */, link };
|
|
810
|
+
}
|
|
811
|
+
if (templateKind === 2 /* SUBGRAPH_ID */) {
|
|
812
|
+
const { id } = await prompts({
|
|
813
|
+
type: "text",
|
|
814
|
+
name: "id",
|
|
815
|
+
message: "Enter a subgraph deployment ID",
|
|
816
|
+
initial: "QmNus..."
|
|
817
|
+
});
|
|
818
|
+
template = { kind: 2 /* SUBGRAPH_ID */, id };
|
|
819
|
+
}
|
|
820
|
+
if (templateKind === 3 /* SUBGRAPH_REPO */) {
|
|
821
|
+
const { path: path8 } = await prompts({
|
|
822
|
+
type: "text",
|
|
823
|
+
name: "path",
|
|
824
|
+
message: "Enter a path to a subgraph repository",
|
|
825
|
+
initial: "../subgraph"
|
|
826
|
+
});
|
|
827
|
+
template = { kind: 3 /* SUBGRAPH_REPO */, path: path8 };
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
const validatedOptions = {
|
|
831
|
+
projectName,
|
|
832
|
+
rootDir: path7.resolve(".", options.dir ? options.dir : projectName),
|
|
833
|
+
template,
|
|
834
|
+
etherscanApiKey: options.etherscanApiKey
|
|
835
|
+
};
|
|
836
|
+
await run(validatedOptions);
|
|
837
|
+
};
|
|
838
|
+
createPonder();
|
|
839
|
+
//# sourceMappingURL=create-ponder.mjs.map
|