create-ponder 0.1.6 → 0.1.8

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/index.js CHANGED
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { writeFileSync as writeFileSync2 } from "node:fs";
5
- import path3 from "node:path";
4
+ import { writeFileSync as writeFileSync3 } from "node:fs";
5
+ import path4 from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { cac } from "cac";
8
8
  import cpy from "cpy";
9
9
  import { execa } from "execa";
10
10
  import fs2 from "fs-extra";
11
11
  import { oraPromise } from "ora";
12
- import pico4 from "picocolors";
13
- import prettier2 from "prettier";
12
+ import pico5 from "picocolors";
13
+ import prettier3 from "prettier";
14
14
  import { default as prompts } from "prompts";
15
15
 
16
16
  // package.json
17
17
  var package_default = {
18
18
  name: "create-ponder",
19
- version: "0.1.6",
19
+ version: "0.1.8",
20
20
  type: "module",
21
21
  description: "A CLI tool to create Ponder apps",
22
22
  license: "MIT",
@@ -47,7 +47,9 @@ var package_default = {
47
47
  prettier: "^3.1.0",
48
48
  prompts: "^2.4.2",
49
49
  "update-check": "^1.5.4",
50
- "validate-npm-package-name": "^5.0.0"
50
+ "validate-npm-package-name": "^5.0.0",
51
+ viem: "^2.0.10",
52
+ yaml: "^2.3.4"
51
53
  },
52
54
  devDependencies: {
53
55
  "@types/fs-extra": "^11.0.4",
@@ -67,118 +69,83 @@ var package_default = {
67
69
  // src/etherscan.ts
68
70
  import { mkdirSync, writeFileSync } from "node:fs";
69
71
  import path from "node:path";
70
- import prettier from "prettier";
71
-
72
- // src/helpers/getEtherscanChainId.ts
73
- var networkByEtherscanHostname = {
74
- "etherscan.io": {
75
- name: "mainnet",
76
- chainId: 1,
77
- apiUrl: "https://api.etherscan.io/api"
78
- },
79
- "ropsten.etherscan.io": {
80
- name: "ropsten",
81
- chainId: 3,
82
- apiUrl: "https://api-ropsten.etherscan.io/api"
83
- },
84
- "rinkeby.etherscan.io": {
85
- name: "rinkeby",
86
- chainId: 4,
87
- apiUrl: "https://api-rinkeby.etherscan.io/api"
88
- },
89
- "goerli.etherscan.io": {
90
- name: "goerli",
91
- chainId: 5,
92
- apiUrl: "https://api-goerli.etherscan.io/api"
93
- },
94
- "kovan.etherscan.io": {
95
- name: "kovan",
96
- chainId: 42,
97
- apiUrl: "https://api-kovan.etherscan.io/api"
98
- },
99
- "sepolia.etherscan.io": {
100
- name: "sepolia",
101
- chainId: 11155111,
102
- apiUrl: "https://api-sepolia.etherscan.io/api"
103
- },
104
- "optimistic.etherscan.io": {
105
- name: "optimism",
106
- chainId: 10,
107
- apiUrl: "https://api-optimistic.etherscan.io/api"
108
- },
109
- "goerli-optimism.etherscan.io": {
110
- name: "optimism-goerli",
111
- chainId: 420,
112
- apiUrl: "https://api-goerli-optimistic.etherscan.io/api"
113
- },
114
- "polygonscan.com": {
115
- name: "polygon",
116
- chainId: 137,
117
- apiUrl: "https://api.polygonscan.com/api"
118
- },
119
- "mumbai.polygonscan.com": {
120
- name: "polygon-mumbai",
121
- chainId: 80001,
122
- apiUrl: "https://api-testnet.polygonscan.com/api"
123
- },
124
- "arbiscan.io": {
125
- name: "arbitrum",
126
- chainId: 42161,
127
- apiUrl: "https://api.arbiscan.io/api"
128
- },
129
- "goerli.arbiscan.io": {
130
- name: "arbitrum-goerli",
131
- chainId: 421613,
132
- apiUrl: "https://api-goerli.arbiscan.io/api"
133
- },
134
- "explorer.zora.energy": {
135
- name: "zora",
136
- chainId: 7777777,
137
- apiUrl: "https://explorer.zora.energy/api"
138
- },
139
- "basescan.org": {
140
- name: "base",
141
- chainId: 8453,
142
- apiUrl: "https://api.basescan.org/api"
143
- },
144
- "goerli.basescan.org": {
145
- name: "base-goerli",
146
- chainId: 84531,
147
- apiUrl: "https://api-goerli.basescan.org/api"
148
- }
149
- };
150
- var getNetworkByEtherscanHostname = (hostname) => {
151
- return networkByEtherscanHostname[hostname];
152
- };
153
72
 
154
73
  // src/helpers/wait.ts
155
74
  var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
156
75
 
157
76
  // src/etherscan.ts
77
+ import pico from "picocolors";
78
+ import prettier from "prettier";
79
+ import * as chains from "viem/chains";
80
+ var chainExplorerByHostname = {};
81
+ for (const [name, chain] of Object.entries(chains)) {
82
+ for (const explorer of Object.values(chain.blockExplorers ?? {})) {
83
+ const hostname = new URL(explorer.url).hostname;
84
+ chainExplorerByHostname[hostname] = {
85
+ name,
86
+ id: chain.id,
87
+ explorer
88
+ };
89
+ }
90
+ }
158
91
  var fromEtherscan = async ({
159
92
  rootDir,
160
93
  etherscanLink,
161
94
  etherscanApiKey
162
95
  }) => {
96
+ const warnings = [];
163
97
  const apiKey = etherscanApiKey || process.env.ETHERSCAN_API_KEY;
164
- const url = new URL(etherscanLink);
165
- const network = getNetworkByEtherscanHostname(url.hostname);
166
- if (!network) {
167
- throw new Error(`Unrecognized etherscan hostname: ${url.hostname}`);
98
+ const explorerUrl = new URL(etherscanLink);
99
+ const chainExplorer = chainExplorerByHostname[explorerUrl.hostname];
100
+ if (!chainExplorer)
101
+ throw new Error(
102
+ `Block explorer (${explorerUrl.hostname}) is not present in viem/chains.`
103
+ );
104
+ const name = chainExplorer.name;
105
+ const chainId = chainExplorer.id;
106
+ const apiUrl = chainExplorer.explorer.apiUrl;
107
+ if (!apiUrl)
108
+ throw new Error(
109
+ `${pico.red("\u2717")} Block explorer (${explorerUrl.hostname}) does not have a API URL registered in viem/chains.`
110
+ );
111
+ const pathComponents = explorerUrl.pathname.slice(1).split("/");
112
+ const contractAddress = pathComponents[1];
113
+ if (pathComponents[0] !== "address" || !(typeof contractAddress === "string") || !contractAddress.startsWith("0x")) {
114
+ throw new Error(
115
+ `${pico.red("\u2717")} Invalid block explorer URL (${explorerUrl.href}). Expected path "/address/<contract-address>".`
116
+ );
168
117
  }
169
- const { name, chainId, apiUrl } = network;
170
- const contractAddress = url.pathname.slice(1).split("/")[1];
118
+ let abiResult = void 0;
119
+ try {
120
+ abiResult = await getContractAbiAndName(contractAddress, apiUrl, apiKey);
121
+ } catch (e) {
122
+ const error = e;
123
+ throw new Error(
124
+ `${pico.red("\u2717")} Failed to fetch contract ABI from block explorer API: ${error.message}`
125
+ );
126
+ }
127
+ if (typeof abiResult.abi !== "string")
128
+ throw new Error(
129
+ `${pico.red(
130
+ "\u2717"
131
+ )} Invalid ABI returned from block explorer API. Is the contract verified?`
132
+ );
133
+ const baseAbi = JSON.parse(abiResult.abi);
134
+ let contractName = abiResult.contractName;
135
+ const abis = [
136
+ { abi: baseAbi, contractName }
137
+ ];
171
138
  let blockNumber = void 0;
172
139
  try {
140
+ if (!apiKey)
141
+ await wait(5e3);
173
142
  const txHash = await getContractCreationTxn(
174
143
  contractAddress,
175
144
  apiUrl,
176
145
  apiKey
177
146
  );
178
- if (!apiKey) {
179
- console.log("\n(1/n) Waiting 5 seconds for Etherscan API rate limit");
147
+ if (!apiKey)
180
148
  await wait(5e3);
181
- }
182
149
  const contractCreationBlockNumber = await getTxBlockNumber(
183
150
  txHash,
184
151
  apiUrl,
@@ -187,47 +154,30 @@ var fromEtherscan = async ({
187
154
  blockNumber = contractCreationBlockNumber;
188
155
  } catch (error) {
189
156
  }
190
- if (!apiKey) {
191
- console.log("(2/n) Waiting 5 seconds for Etherscan API rate limit");
192
- await wait(5e3);
193
- }
194
- const abis = [];
195
- const abiAndName = await getContractAbiAndName(
196
- contractAddress,
197
- apiUrl,
198
- apiKey
199
- );
200
- const { abi } = abiAndName;
201
- let contractName = abiAndName.contractName;
202
- abis.push({ abi: JSON.parse(abi), contractName });
203
- if (JSON.parse(abi).find(
157
+ if (baseAbi.find(
204
158
  (item) => item.type === "event" && item.name === "Upgraded" && item.inputs[0].name === "implementation"
205
159
  )) {
206
- console.log(
207
- "Detected EIP-1967 proxy, fetching implementation contract ABIs"
208
- );
209
- if (!apiKey) {
210
- console.log("(3/n) Waiting 5 seconds for Etherscan API rate limit");
160
+ if (!apiKey)
211
161
  await wait(5e3);
212
- }
213
162
  const { implAddresses } = await getProxyImplementationAddresses({
214
163
  contractAddress,
215
164
  apiUrl,
216
165
  fromBlock: blockNumber,
217
166
  apiKey
218
167
  });
219
- for (const [index, implAddress] of implAddresses.entries()) {
220
- console.log(`Fetching ABI for implementation contract: ${implAddress}`);
221
- if (!apiKey) {
222
- console.log(
223
- `(${4 + index}/${4 + implAddresses.length - 1}) Waiting 5 seconds for Etherscan API rate limit`
224
- );
168
+ for (const implAddress of implAddresses) {
169
+ if (!apiKey)
225
170
  await wait(5e3);
171
+ const { abi, contractName: implContractName } = await getContractAbiAndName(implAddress, apiUrl, apiKey);
172
+ if (typeof abi !== "string") {
173
+ warnings.push(
174
+ `Unable to fetch ABI for implementation contract ${implAddress}. Please see the proxy contract documentation for more details: https://ponder.sh/docs/guides/add-contracts#multiple-abis`
175
+ );
176
+ continue;
226
177
  }
227
- const { abi: abi2, contractName: implContractName } = await getContractAbiAndName(implAddress, apiUrl, apiKey);
228
178
  contractName = implContractName;
229
179
  abis.push({
230
- abi: JSON.parse(abi2),
180
+ abi: JSON.parse(abi),
231
181
  contractName: `${contractName}_${implAddress.slice(0, 6)}`
232
182
  });
233
183
  }
@@ -235,7 +185,7 @@ var fromEtherscan = async ({
235
185
  mkdirSync(path.join(rootDir, "abis"), { recursive: true });
236
186
  mkdirSync(path.join(rootDir, "src"), { recursive: true });
237
187
  let abiConfig;
238
- for (const { abi: abi2, contractName: contractName2 } of abis) {
188
+ for (const { abi, contractName: contractName2 } of abis) {
239
189
  const abiRelativePath = `./abis/${contractName2}Abi.ts`;
240
190
  const abiAbsolutePath = path.join(
241
191
  path.resolve(".", rootDir),
@@ -244,15 +194,13 @@ var fromEtherscan = async ({
244
194
  writeFileSync(
245
195
  abiAbsolutePath,
246
196
  await prettier.format(
247
- `export const ${contractName2}Abi = ${JSON.stringify(abi2)} as const`,
248
- {
249
- parser: "typescript"
250
- }
197
+ `export const ${contractName2}Abi = ${JSON.stringify(abi)} as const`,
198
+ { parser: "typescript" }
251
199
  )
252
200
  );
253
201
  if (abis.length === 1) {
254
202
  abiConfig = {
255
- abi: abi2,
203
+ abi,
256
204
  dir: abiRelativePath,
257
205
  name: `${contractName2}Abi`
258
206
  };
@@ -261,7 +209,7 @@ var fromEtherscan = async ({
261
209
  abiConfig = [];
262
210
  }
263
211
  abiConfig.push({
264
- abi: abi2,
212
+ abi,
265
213
  name: `${contractName2}Abi`,
266
214
  dir: abiRelativePath
267
215
  });
@@ -283,7 +231,7 @@ var fromEtherscan = async ({
283
231
  }
284
232
  }
285
233
  };
286
- return config;
234
+ return { config, warnings };
287
235
  };
288
236
  var fetchEtherscan = async (url) => {
289
237
  const maxRetries = 5;
@@ -292,9 +240,6 @@ var fetchEtherscan = async (url) => {
292
240
  try {
293
241
  const response = await fetch(url);
294
242
  const data = await response.json();
295
- if (data.status === "0") {
296
- throw new Error(`Etherscan API error: ${data.result}`);
297
- }
298
243
  return data;
299
244
  } catch (error) {
300
245
  retryCount++;
@@ -370,7 +315,7 @@ var getProxyImplementationAddresses = async ({
370
315
  };
371
316
 
372
317
  // src/helpers/getPackageManager.ts
373
- import pico from "picocolors";
318
+ import pico2 from "picocolors";
374
319
  var getPackageManager = ({
375
320
  options
376
321
  }) => {
@@ -395,11 +340,11 @@ var getPackageManager = ({
395
340
  if (userAgent.includes("yarn"))
396
341
  return "yarn";
397
342
  }
398
- throw Error(pico.red("Undetectable package manager"));
343
+ throw Error(pico2.red("Undetectable package manager"));
399
344
  };
400
345
 
401
346
  // src/helpers/notifyUpdate.ts
402
- import pico2 from "picocolors";
347
+ import pico3 from "picocolors";
403
348
  import checkForUpdate from "update-check";
404
349
  var log = console.log;
405
350
  async function notifyUpdate({ options }) {
@@ -409,11 +354,11 @@ async function notifyUpdate({ options }) {
409
354
  const packageManager = getPackageManager({ options });
410
355
  const updateMessage = packageManager === "bun" ? "bun install --global create-ponder" : packageManager === "pnpm" ? "pnpm add -g create-ponder" : packageManager === "npm" ? "npm install -g create-ponder" : "yarn global add create-ponder";
411
356
  log(
412
- pico2.bold(
413
- `${pico2.yellow(
357
+ pico3.bold(
358
+ `${pico3.yellow(
414
359
  "A new version of `create-ponder` is available!"
415
360
  )}
416
- You can update by running: ${pico2.cyan(updateMessage)}
361
+ You can update by running: ${pico3.cyan(updateMessage)}
417
362
  `
418
363
  )
419
364
  );
@@ -426,7 +371,7 @@ You can update by running: ${pico2.cyan(updateMessage)}
426
371
  // src/helpers/validate.ts
427
372
  import path2 from "path";
428
373
  import fs from "fs-extra";
429
- import pico3 from "picocolors";
374
+ import pico4 from "picocolors";
430
375
  import validatePackageName from "validate-npm-package-name";
431
376
  async function validateProjectName({
432
377
  projectName,
@@ -477,8 +422,145 @@ async function validateTemplateName({
477
422
  var ValidationError = class extends Error {
478
423
  name = "ValidationError";
479
424
  constructor(validation) {
480
- super([pico3.red(validation.message), validation.problems].join("\n"));
425
+ super([pico4.red(validation.message), validation.problems].join("\n"));
426
+ }
427
+ };
428
+
429
+ // src/subgraph.ts
430
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
431
+ import path3 from "node:path";
432
+ import prettier2 from "prettier";
433
+ import { parse } from "yaml";
434
+
435
+ // src/helpers/getGraphProtocolChainId.ts
436
+ var chainIdByGraphNetwork = {
437
+ mainnet: 1,
438
+ kovan: 42,
439
+ rinkeby: 4,
440
+ ropsten: 3,
441
+ goerli: 5,
442
+ "poa-core": 99,
443
+ "poa-sokol": 77,
444
+ xdai: 100,
445
+ matic: 137,
446
+ mumbai: 80001,
447
+ fantom: 250,
448
+ "fantom-testnet": 4002,
449
+ bsc: 56,
450
+ chapel: -1,
451
+ clover: 0,
452
+ avalanche: 43114,
453
+ fuji: 43113,
454
+ celo: 42220,
455
+ "celo-alfajores": 44787,
456
+ fuse: 122,
457
+ moonbeam: 1284,
458
+ moonriver: 1285,
459
+ mbase: -1,
460
+ "arbitrum-one": 42161,
461
+ "arbitrum-rinkeby": 421611,
462
+ optimism: 10,
463
+ "optimism-kovan": 69,
464
+ aurora: 1313161554,
465
+ "aurora-testnet": 1313161555
466
+ };
467
+ var getGraphProtocolChainId = (networkName) => {
468
+ return chainIdByGraphNetwork[networkName];
469
+ };
470
+ var subgraphYamlFileNames = ["subgraph.yaml"].concat(
471
+ Object.keys(chainIdByGraphNetwork).map((n) => `subgraph-${n}.yaml`)
472
+ );
473
+
474
+ // src/helpers/validateGraphProtocolSource.ts
475
+ var validateGraphProtocolSource = (source) => {
476
+ return source;
477
+ };
478
+
479
+ // src/subgraph.ts
480
+ var fetchIpfsFile = async (cid) => {
481
+ const url = `https://ipfs.network.thegraph.com/api/v0/cat?arg=${cid}`;
482
+ const response = await fetch(url);
483
+ const contentRaw = await response.text();
484
+ return contentRaw;
485
+ };
486
+ var fromSubgraphId = async ({
487
+ rootDir,
488
+ subgraphId
489
+ }) => {
490
+ const manifestRaw = await fetchIpfsFile(subgraphId);
491
+ const manifest = parse(manifestRaw);
492
+ const contracts = {};
493
+ manifest.dataSources.forEach((d) => {
494
+ contracts[d.name] = {
495
+ network: d.network,
496
+ address: d.source.address,
497
+ startBlock: d.source.startBlock
498
+ };
499
+ });
500
+ const dataSources = manifest.dataSources;
501
+ mkdirSync2(path3.join(rootDir, "abis"), { recursive: true });
502
+ mkdirSync2(path3.join(rootDir, "src"), { recursive: true });
503
+ const abiFiles = dataSources.flatMap((source) => validateGraphProtocolSource(source).mapping.abis).filter(
504
+ (source, idx, arr) => arr.findIndex((s) => s.name === source.name) === idx
505
+ );
506
+ const abis = {};
507
+ await Promise.all(
508
+ abiFiles.map(async (abi) => {
509
+ const abiContent = await fetchIpfsFile(abi.file["/"].slice(6));
510
+ const abiPath = path3.join(rootDir, `./abis/${abi.name}Abi.ts`);
511
+ writeFileSync2(
512
+ abiPath,
513
+ await prettier2.format(
514
+ `export const ${abi.name}Abi = ${abiContent} as const`,
515
+ { parser: "typescript" }
516
+ )
517
+ );
518
+ abis[abi.name] = JSON.parse(abiContent);
519
+ })
520
+ );
521
+ const ponderContracts = dataSources.map((sourceInvalid) => {
522
+ const source = validateGraphProtocolSource(sourceInvalid);
523
+ const network = source.network || "mainnet";
524
+ const chainId = getGraphProtocolChainId(network);
525
+ if (!chainId || chainId === -1) {
526
+ throw new Error(`Unhandled network name: ${network}`);
527
+ }
528
+ const abiRelativePath = `./abis/${source.source.abi}Abi.ts`;
529
+ return {
530
+ name: source.name,
531
+ network,
532
+ address: source.source.address,
533
+ abi: {
534
+ abi: abis[source.source.abi],
535
+ dir: abiRelativePath,
536
+ name: `${source.source.abi}Abi`
537
+ },
538
+ startBlock: source.source.startBlock
539
+ };
540
+ });
541
+ const contractsObject = {};
542
+ const networksObject = {};
543
+ ponderContracts.forEach((pc) => {
544
+ contractsObject[pc.name] = pc;
545
+ networksObject[pc.network] = {
546
+ chainId: getGraphProtocolChainId(pc.network),
547
+ transport: `http(process.env.PONDER_RPC_URL_${getGraphProtocolChainId(
548
+ pc.network
549
+ )})`
550
+ };
551
+ contractsObject[pc.name].name = void 0;
552
+ });
553
+ const config = {
554
+ networks: networksObject,
555
+ contracts: contractsObject
556
+ };
557
+ const warnings = [];
558
+ if (manifest.templates?.length > 0) {
559
+ warnings.push(
560
+ "Factory contract detected. Please see the factory contract documentation for more details: https://ponder.sh/docs/guides/add-contracts#factory-contracts"
561
+ );
481
562
  }
563
+ return { config, warnings };
482
564
  };
483
565
 
484
566
  // src/index.ts
@@ -488,7 +570,12 @@ var templates = [
488
570
  {
489
571
  id: "etherscan",
490
572
  title: "Etherscan contract link",
491
- description: "Bootstrap from an Etherscan contract link"
573
+ description: "Create from an Etherscan contract link"
574
+ },
575
+ {
576
+ id: "subgraph",
577
+ title: "Subgraph ID",
578
+ description: "Create from a deployed subgraph"
492
579
  },
493
580
  {
494
581
  id: "feature-factory",
@@ -542,15 +629,16 @@ async function run({
542
629
  }) {
543
630
  if (options.help)
544
631
  return;
632
+ const warnings = [];
545
633
  log2();
546
634
  log2(
547
- `Welcome to ${pico4.bold(
548
- pico4.blue("create-ponder")
635
+ `Welcome to ${pico5.bold(
636
+ pico5.blue("create-ponder")
549
637
  )} \u2013 the quickest way to get started with Ponder!`
550
638
  );
551
639
  log2();
552
640
  const __dirname = fileURLToPath(new URL(".", import.meta.url));
553
- const templatesPath = path3.join(__dirname, "..", "templates");
641
+ const templatesPath = path4.join(__dirname, "..", "templates");
554
642
  let templateId = options.template || options.t;
555
643
  let templateValidation = await validateTemplateName({
556
644
  isNameRequired: false,
@@ -565,7 +653,7 @@ async function run({
565
653
  projectPath = args[0].trim();
566
654
  const splitPath = projectPath.split("/");
567
655
  projectName = splitPath[splitPath.length - 1]?.trim() || "";
568
- log2(pico4.green("\u2714"), pico4.bold("Using project name:"), projectName);
656
+ log2(pico5.green("\u2714"), pico5.bold("Using project name:"), projectName);
569
657
  } else {
570
658
  const res = await prompts({
571
659
  initial: "my-app",
@@ -591,8 +679,10 @@ async function run({
591
679
  });
592
680
  if (!nameValidation.valid)
593
681
  throw new ValidationError(nameValidation);
594
- if (options.etherscanContractLink && !templateId)
682
+ if (options.etherscan && !templateId)
595
683
  templateId = "etherscan";
684
+ if (options.subgraph && !templateId)
685
+ templateId = "subgraph";
596
686
  if (!templateId) {
597
687
  templateId = (await prompts({
598
688
  name: "templateId",
@@ -614,30 +704,69 @@ async function run({
614
704
  if (!templateValidation.valid)
615
705
  throw new ValidationError(templateValidation);
616
706
  let config;
617
- const targetPath = path3.join(process.cwd(), projectPath);
707
+ const targetPath = path4.join(process.cwd(), projectPath);
708
+ let url = options.etherscan;
618
709
  if (templateMeta.id === "etherscan") {
619
- let link = options.etherscanContractLink;
620
- if (!link) {
710
+ if (!url) {
621
711
  const result = await prompts({
622
712
  type: "text",
623
- name: "link",
624
- message: "Enter an Etherscan contract link",
713
+ name: "url",
714
+ message: "Enter a block explorer contract url",
625
715
  initial: "https://etherscan.io/address/0x97..."
626
716
  });
627
- link = result.link;
717
+ url = result.url;
718
+ }
719
+ }
720
+ let subgraph = options.subgraph;
721
+ if (templateMeta.id === "subgraph") {
722
+ if (!subgraph) {
723
+ const result = await prompts({
724
+ type: "text",
725
+ name: "id",
726
+ message: "Enter a subgraph ID",
727
+ initial: "Qmb3hd2hYd2nWFgcmRswykF1dUBSrDUrinYCgN1dmE1tNy"
728
+ });
729
+ subgraph = result.id;
730
+ }
731
+ if (!subgraph) {
732
+ log2(pico5.red("No subgraph ID provided."));
733
+ process.exit(0);
628
734
  }
629
- config = await fromEtherscan({
630
- rootDir: targetPath,
631
- etherscanLink: link,
632
- etherscanApiKey: options.etherscanApiKey
633
- });
634
735
  }
635
- log2(`Creating a repo at: ${pico4.green(targetPath)}`);
636
- log2();
637
- log2(`Using template: ${pico4.bold(templateMeta.title)}`);
638
736
  log2();
639
- const templatePath = path3.join(templatesPath, templateMeta.id);
640
- await cpy(path3.join(templatePath, "**", "*"), targetPath, {
737
+ if (templateMeta.id === "etherscan") {
738
+ const host = new URL(url).host;
739
+ const result = await oraPromise(
740
+ fromEtherscan({
741
+ rootDir: targetPath,
742
+ etherscanLink: url,
743
+ etherscanApiKey: options.etherscanApiKey
744
+ }),
745
+ {
746
+ text: `Fetching contract metadata from ${pico5.bold(
747
+ host
748
+ )}. This may take a few seconds.`,
749
+ failText: "Failed to fetch contract metadata.",
750
+ successText: `Fetched contract metadata from ${pico5.bold(host)}.`
751
+ }
752
+ );
753
+ config = result.config;
754
+ warnings.push(...result.warnings);
755
+ }
756
+ if (templateMeta.id === "subgraph") {
757
+ const result = await oraPromise(
758
+ fromSubgraphId({ rootDir: targetPath, subgraphId: subgraph }),
759
+ {
760
+ text: "Fetching subgraph metadata. This may take a few seconds.",
761
+ failText: "Failed to fetch subgraph metadata.",
762
+ successText: `Fetched subgraph metadata for ${pico5.bold(subgraph)}.`
763
+ }
764
+ );
765
+ config = result.config;
766
+ warnings.push(...result.warnings);
767
+ }
768
+ const templatePath = path4.join(templatesPath, templateMeta.id);
769
+ await cpy(path4.join(templatePath, "**", "*"), targetPath, {
641
770
  rename: (name) => name.replace(/^_dot_/, ".")
642
771
  });
643
772
  if (config) {
@@ -645,7 +774,9 @@ async function run({
645
774
  import { createConfig${Object.values(config.contracts).some((c) => Array.isArray(c.abi)) ? ", mergeAbis" : ""} } from "@ponder/core";
646
775
  import { http } from "viem";
647
776
 
648
- ${Object.values(config.contracts).flatMap((c) => c.abi).map(
777
+ ${Object.values(config.contracts).flatMap((c) => c.abi).filter(
778
+ (tag, index, array) => array.findIndex((t) => t.dir === tag.dir) === index
779
+ ).map(
649
780
  (abi) => `import {${abi.name}} from "${abi.dir.slice(
650
781
  0,
651
782
  abi.dir.length - 3
@@ -671,14 +802,14 @@ async function run({
671
802
  ).replaceAll(/"abi":"(.*?)"/g, "abi:$1")},
672
803
  });
673
804
  `;
674
- writeFileSync2(
675
- path3.join(targetPath, "ponder.config.ts"),
676
- await prettier2.format(configContent, { parser: "typescript" })
805
+ writeFileSync3(
806
+ path4.join(targetPath, "ponder.config.ts"),
807
+ await prettier3.format(configContent, { parser: "typescript" })
677
808
  );
678
809
  for (const [name, contract] of Object.entries(config.contracts)) {
679
810
  const abi = Array.isArray(contract.abi) ? contract.abi[1].abi : contract.abi.abi;
680
811
  const abiEvents = abi.filter(
681
- (item) => item.type === "event"
812
+ (item) => item.type === "event" && !item.anonymous
682
813
  );
683
814
  const eventNamesToWrite = abiEvents.map((event) => event.name).slice(0, 2);
684
815
  const indexingFunctionFileContents = `
@@ -691,25 +822,23 @@ async function run({
691
822
  })`
692
823
  ).join("\n")}
693
824
  `;
694
- writeFileSync2(
695
- path3.join(targetPath, `./src/${name}.ts`),
696
- await prettier2.format(indexingFunctionFileContents, {
825
+ writeFileSync3(
826
+ path4.join(targetPath, `./src/${name}.ts`),
827
+ await prettier3.format(indexingFunctionFileContents, {
697
828
  parser: "typescript"
698
829
  })
699
830
  );
700
831
  }
701
832
  }
702
- const packageJson = await fs2.readJSON(path3.join(targetPath, "package.json"));
833
+ const packageJson = await fs2.readJSON(path4.join(targetPath, "package.json"));
703
834
  packageJson.name = projectName;
704
835
  packageJson.dependencies["@ponder/core"] = `^${package_default.version}`;
705
836
  packageJson.devDependencies["eslint-config-ponder"] = `^${package_default.version}`;
706
837
  await fs2.writeFile(
707
- path3.join(targetPath, "package.json"),
838
+ path4.join(targetPath, "package.json"),
708
839
  JSON.stringify(packageJson, null, 2)
709
840
  );
710
841
  const packageManager = getPackageManager({ options });
711
- log2(`Installing with: ${pico4.bold(packageManager)}`);
712
- log2();
713
842
  const installArgs = [
714
843
  "install",
715
844
  packageManager === "npm" ? "--quiet" : "--silent"
@@ -727,54 +856,70 @@ async function run({
727
856
  }
728
857
  }),
729
858
  {
730
- text: "Installing packages. This may take a few seconds.",
859
+ text: `Installing packages with ${pico5.bold(
860
+ packageManager
861
+ )}. This may take a few seconds.`,
731
862
  failText: "Failed to install packages.",
732
- successText: "Installed packages."
863
+ successText: `Installed packages with ${pico5.bold(packageManager)}.`
733
864
  }
734
865
  );
735
- log2();
736
866
  if (!options.skipGit) {
737
- await execa("git", ["init"], { cwd: targetPath });
738
- await execa("git", ["add", "."], { cwd: targetPath });
739
- await execa(
740
- "git",
741
- [
742
- "commit",
743
- "--no-verify",
744
- "--message",
745
- "chore: initial commit from create-ponder"
746
- ],
747
- { cwd: targetPath }
867
+ await oraPromise(
868
+ async () => {
869
+ await execa("git", ["init"], { cwd: targetPath });
870
+ await execa("git", ["add", "."], { cwd: targetPath });
871
+ await execa(
872
+ "git",
873
+ [
874
+ "commit",
875
+ "--no-verify",
876
+ "--message",
877
+ "chore: initial commit from create-ponder"
878
+ ],
879
+ { cwd: targetPath }
880
+ );
881
+ },
882
+ {
883
+ text: "Initializing git repository.",
884
+ failText: "Failed to initialize git repository.",
885
+ successText: "Initialized git repository."
886
+ }
748
887
  );
749
- log2(pico4.green("\u2714"), "Initialized git repository.");
888
+ }
889
+ for (const warning of warnings) {
750
890
  log2();
891
+ log2(pico5.yellow(warning));
751
892
  }
893
+ log2();
752
894
  log2("\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015");
753
895
  log2();
754
896
  log2(
755
- `${pico4.green("Success!")} Created ${pico4.bold(
897
+ `${pico5.green("Success!")} Created ${pico5.bold(
756
898
  projectName
757
- )} at ${pico4.green(path3.resolve(projectPath))}`
899
+ )} at ${pico5.green(path4.resolve(projectPath))}`
758
900
  );
759
901
  log2();
760
902
  log2(
761
- `To start your app, run \`${pico4.bold(
762
- pico4.cyan(`cd ${projectPath}`)
763
- )}\` and then \`${pico4.bold(
764
- pico4.cyan(
903
+ `To start your app, run ${pico5.bold(
904
+ pico5.cyan(`cd ${projectPath}`)
905
+ )} and then ${pico5.bold(
906
+ pico5.cyan(
765
907
  `${packageManager}${packageManager === "npm" || packageManager === "bun" ? " run" : ""} dev`
766
908
  )
767
- )}\``
909
+ )}`
768
910
  );
769
911
  log2();
770
912
  log2("\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015\u2015");
771
913
  log2();
772
914
  }
773
915
  (async () => {
774
- const cli = cac(package_default.name).version(package_default.version).usage(`${pico4.green("<project-directory>")} [options]`).option(
775
- "-t, --template [name]",
776
- `A template to bootstrap with. Available: ${templates.map(({ id }) => id).join(", ")}`
777
- ).option("--etherscan-contract-link [link]", "Etherscan contract link").option("--etherscan-api-key [key]", "Etherscan API key").option("--npm", "Use npm as your package manager").option("--pnpm", "Use pnpm as your package manager").option("--yarn", "Use yarn as your package manager").option("--skip-git", "Skips initializing the project as a git repository").help();
916
+ const cli = cac(package_default.name).version(package_default.version).usage(`${pico5.green("<directory>")} [options]`).option(
917
+ "-t, --template [id]",
918
+ `Use a template. Options: ${templates.map(({ id }) => id).join(", ")}`
919
+ ).option("--etherscan [url]", "Use the Etherscan template").option("--subgraph [id]", "Use the subgraph template").option("--npm", "Use npm as your package manager").option("--pnpm", "Use pnpm as your package manager").option("--yarn", "Use yarn as your package manager").option("--skip-git", "Skip initializing a git repository").option(
920
+ "--etherscan-api-key [key]",
921
+ "Etherscan API key for Etherscan template"
922
+ ).help();
778
923
  const _nodeVersion = process.version.split(".");
779
924
  const nodeVersion = [
780
925
  Number(_nodeVersion[0].slice(1)),
@@ -783,7 +928,7 @@ async function run({
783
928
  ];
784
929
  if (nodeVersion[0] < 18 || nodeVersion[0] === 18 && nodeVersion[1] < 14)
785
930
  throw Error(
786
- pico4.red(
931
+ pico5.red(
787
932
  `Node version:${process.version} does not meet the >=18.14 requirement`
788
933
  )
789
934
  );
@@ -794,7 +939,7 @@ async function run({
794
939
  await notifyUpdate({ options });
795
940
  } catch (error) {
796
941
  log2(
797
- error instanceof ValidationError ? error.message : pico4.red(error.message)
942
+ error instanceof ValidationError ? error.message : pico5.red(error.message)
798
943
  );
799
944
  log2();
800
945
  await notifyUpdate({ options });