create-ponder 0.2.0-next.1 → 0.2.0-next.2

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