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 +352 -207
- package/package.json +4 -2
- package/templates/empty/ponder-env.d.ts +25 -3
- package/templates/etherscan/ponder-env.d.ts +25 -3
- package/templates/feature-factory/ponder-env.d.ts +25 -3
- package/templates/feature-filter/ponder-env.d.ts +25 -3
- package/templates/feature-multichain/ponder-env.d.ts +25 -3
- package/templates/feature-proxy/ponder-env.d.ts +25 -3
- package/templates/feature-read-contract/ponder-env.d.ts +25 -3
- package/templates/project-friendtech/ponder-env.d.ts +25 -3
- package/templates/project-uniswap-v3-flash/ponder-env.d.ts +25 -3
- package/templates/reference-erc20/ponder-env.d.ts +25 -3
- package/templates/reference-erc721/ponder-env.d.ts +25 -3
- package/templates/subgraph/_dot_env.local +5 -0
- package/templates/subgraph/_dot_eslintrc.json +3 -0
- package/templates/subgraph/_dot_gitignore +18 -0
- package/templates/subgraph/package.json +26 -0
- package/templates/subgraph/ponder-env.d.ts +32 -0
- package/templates/subgraph/ponder.schema.ts +8 -0
- 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
|
|
5
|
-
import
|
|
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
|
|
13
|
-
import
|
|
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.
|
|
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
|
|
165
|
-
const
|
|
166
|
-
if (!
|
|
167
|
-
throw new Error(
|
|
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
|
-
|
|
170
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
220
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
343
|
+
throw Error(pico2.red("Undetectable package manager"));
|
|
399
344
|
};
|
|
400
345
|
|
|
401
346
|
// src/helpers/notifyUpdate.ts
|
|
402
|
-
import
|
|
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
|
-
|
|
413
|
-
`${
|
|
357
|
+
pico3.bold(
|
|
358
|
+
`${pico3.yellow(
|
|
414
359
|
"A new version of `create-ponder` is available!"
|
|
415
360
|
)}
|
|
416
|
-
You can update by running: ${
|
|
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
|
|
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([
|
|
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: "
|
|
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 ${
|
|
548
|
-
|
|
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 =
|
|
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(
|
|
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.
|
|
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 =
|
|
707
|
+
const targetPath = path4.join(process.cwd(), projectPath);
|
|
708
|
+
let url = options.etherscan;
|
|
618
709
|
if (templateMeta.id === "etherscan") {
|
|
619
|
-
|
|
620
|
-
if (!link) {
|
|
710
|
+
if (!url) {
|
|
621
711
|
const result = await prompts({
|
|
622
712
|
type: "text",
|
|
623
|
-
name: "
|
|
624
|
-
message: "Enter
|
|
713
|
+
name: "url",
|
|
714
|
+
message: "Enter a block explorer contract url",
|
|
625
715
|
initial: "https://etherscan.io/address/0x97..."
|
|
626
716
|
});
|
|
627
|
-
|
|
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
|
-
|
|
640
|
-
|
|
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).
|
|
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
|
-
|
|
675
|
-
|
|
676
|
-
await
|
|
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
|
-
|
|
695
|
-
|
|
696
|
-
await
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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:
|
|
863
|
+
successText: `Installed packages with ${pico5.bold(packageManager)}.`
|
|
733
864
|
}
|
|
734
865
|
);
|
|
735
|
-
log2();
|
|
736
866
|
if (!options.skipGit) {
|
|
737
|
-
await
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
-
|
|
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
|
-
`${
|
|
897
|
+
`${pico5.green("Success!")} Created ${pico5.bold(
|
|
756
898
|
projectName
|
|
757
|
-
)} at ${
|
|
899
|
+
)} at ${pico5.green(path4.resolve(projectPath))}`
|
|
758
900
|
);
|
|
759
901
|
log2();
|
|
760
902
|
log2(
|
|
761
|
-
`To start your app, run
|
|
762
|
-
|
|
763
|
-
)}
|
|
764
|
-
|
|
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(`${
|
|
775
|
-
"-t, --template [
|
|
776
|
-
`
|
|
777
|
-
).option("--etherscan
|
|
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
|
-
|
|
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 :
|
|
942
|
+
error instanceof ValidationError ? error.message : pico5.red(error.message)
|
|
798
943
|
);
|
|
799
944
|
log2();
|
|
800
945
|
await notifyUpdate({ options });
|