@@ -23,7 +23,13 @@ import EthereumABI from '../protocols/ethereum/abi.js';
23
23
import Protocol , { ProtocolName } from '../protocols/index.js' ;
24
24
import { abiEvents } from '../scaffold/schema.js' ;
25
25
import Schema from '../schema.js' ;
26
- import { createIpfsClient , loadSubgraphSchemaFromIPFS } from '../utils.js' ;
26
+ import {
27
+ createIpfsClient ,
28
+ getMinStartBlock ,
29
+ loadManifestYaml ,
30
+ loadSubgraphSchemaFromIPFS ,
31
+ validateSubgraphNetworkMatch ,
32
+ } from '../utils.js' ;
27
33
import { validateContract } from '../validation/index.js' ;
28
34
import AddCommand from './add.js' ;
29
35
@@ -54,6 +60,10 @@ export default class InitCommand extends Command {
54
60
summary : 'Graph node for which to initialize.' ,
55
61
char : 'g' ,
56
62
} ) ,
63
+ 'from-subgraph' : Flags . string ( {
64
+ description : 'Creates a scaffold based on an existing subgraph.' ,
65
+ exclusive : [ 'from-example' , 'from-contract' ] ,
66
+ } ) ,
57
67
'from-contract' : Flags . string ( {
58
68
description : 'Creates a scaffold based on an existing contract.' ,
59
69
exclusive : [ 'from-example' ] ,
@@ -88,7 +98,6 @@ export default class InitCommand extends Command {
88
98
description : 'Block number to start indexing from.' ,
89
99
// TODO: using a default sets the value and therefore requires --from-contract
90
100
// default: '0',
91
- dependsOn : [ 'from-contract' ] ,
92
101
} ) ,
93
102
94
103
abi : Flags . string ( {
@@ -110,7 +119,6 @@ export default class InitCommand extends Command {
110
119
summary : 'IPFS node to use for fetching subgraph data.' ,
111
120
char : 'i' ,
112
121
default : DEFAULT_IPFS_URL ,
113
- hidden : true ,
114
122
} ) ,
115
123
} ;
116
124
@@ -127,6 +135,7 @@ export default class InitCommand extends Command {
127
135
protocol,
128
136
node : nodeFlag ,
129
137
'from-contract' : fromContract ,
138
+ 'from-subgraph' : fromSubgraph ,
130
139
'contract-name' : contractName ,
131
140
'from-example' : fromExample ,
132
141
'index-events' : indexEvents ,
@@ -141,11 +150,20 @@ export default class InitCommand extends Command {
141
150
142
151
initDebugger ( 'Flags: %O' , flags ) ;
143
152
153
+ if ( startBlock && ! ( fromContract || fromSubgraph ) ) {
154
+ this . error ( '--start-block can only be used with --from-contract or --from-subgraph' ) ;
155
+ }
156
+
157
+ if ( fromContract && fromSubgraph ) {
158
+ this . error ( 'Cannot use both --from-contract and --from-subgraph at the same time' ) ;
159
+ }
160
+
144
161
if ( skipGit ) {
145
162
this . warn (
146
163
'The --skip-git flag will be removed in the next major version. By default we will stop initializing a Git repository.' ,
147
164
) ;
148
165
}
166
+
149
167
if ( ( fromContract || spkgPath ) && ! network && ! fromExample ) {
150
168
this . error ( '--network is required when using --from-contract or --spkg' ) ;
151
169
}
@@ -199,16 +217,15 @@ export default class InitCommand extends Command {
199
217
let abi ! : EthereumABI ;
200
218
201
219
// If all parameters are provided from the command-line,
202
- // go straight to creating the subgraph from an existing contract
203
- if ( ( fromContract || spkgPath ) && protocol && subgraphName && directory && network && node ) {
204
- const registry = await loadRegistry ( ) ;
205
- const contractService = new ContractService ( registry ) ;
206
- const sourcifyContractInfo = await contractService . getFromSourcify (
207
- EthereumABI ,
208
- network ,
209
- fromContract ! ,
210
- ) ;
211
-
220
+ // go straight to creating the subgraph from an existing contract or source subgraph
221
+ if (
222
+ ( fromContract || spkgPath || fromSubgraph ) &&
223
+ protocol &&
224
+ subgraphName &&
225
+ directory &&
226
+ network &&
227
+ node
228
+ ) {
212
229
if ( ! protocolChoices . includes ( protocol as ProtocolName ) ) {
213
230
this . error (
214
231
`Protocol '${ protocol } ' is not supported, choose from these options: ${ protocolChoices . join (
@@ -220,7 +237,31 @@ export default class InitCommand extends Command {
220
237
221
238
const protocolInstance = new Protocol ( protocol as ProtocolName ) ;
222
239
223
- if ( protocolInstance . hasABIs ( ) ) {
240
+ if ( fromSubgraph && ! protocolInstance . isComposedSubgraph ( ) ) {
241
+ this . error ( '--protocol can only be subgraph when using --from-subgraph' ) ;
242
+ }
243
+
244
+ if (
245
+ fromContract &&
246
+ ( protocolInstance . isComposedSubgraph ( ) || protocolInstance . isSubstreams ( ) )
247
+ ) {
248
+ this . error ( '--protocol cannot be subgraph or substreams when using --from-contract' ) ;
249
+ }
250
+
251
+ if ( spkgPath && ! protocolInstance . isSubstreams ( ) ) {
252
+ this . error ( '--protocol can only be substreams when using --spkg' ) ;
253
+ }
254
+
255
+ // Only fetch contract info and ABI for non-source-subgraph cases
256
+ if ( ! fromSubgraph && protocolInstance . hasABIs ( ) ) {
257
+ const registry = await loadRegistry ( ) ;
258
+ const contractService = new ContractService ( registry ) ;
259
+ const sourcifyContractInfo = await contractService . getFromSourcify (
260
+ EthereumABI ,
261
+ network ,
262
+ fromContract ! ,
263
+ ) ;
264
+
224
265
const ABI = protocolInstance . getABI ( ) ;
225
266
if ( abiPath ) {
226
267
try {
@@ -244,7 +285,7 @@ export default class InitCommand extends Command {
244
285
protocolInstance,
245
286
abi,
246
287
directory,
247
- source : fromContract ! ,
288
+ source : fromSubgraph || fromContract ! ,
248
289
indexEvents,
249
290
network,
250
291
subgraphName,
@@ -288,7 +329,7 @@ export default class InitCommand extends Command {
288
329
abi,
289
330
abiPath,
290
331
directory,
291
- source : fromContract ,
332
+ source : fromContract || fromSubgraph ,
292
333
indexEvents,
293
334
fromExample,
294
335
subgraphName,
@@ -534,7 +575,7 @@ async function processInitForm(
534
575
value : 'contract' ,
535
576
} ,
536
577
{ message : 'Substreams' , name : 'substreams' , value : 'substreams' } ,
537
- // { message: 'Subgraph', name: 'subgraph', value: 'subgraph' },
578
+ { message : 'Subgraph' , name : 'subgraph' , value : 'subgraph' } ,
538
579
] . filter ( ( { name } ) => name ) ,
539
580
} ) ;
540
581
@@ -604,6 +645,30 @@ async function processInitForm(
604
645
} ,
605
646
} ) ;
606
647
648
+ promptManager . addStep ( {
649
+ type : 'input' ,
650
+ name : 'ipfs' ,
651
+ message : `IPFS node to use for fetching subgraph manifest` ,
652
+ initial : ipfsUrl ,
653
+ skip : ( ) => ! isComposedSubgraph ,
654
+ validate : value => {
655
+ if ( ! value ) {
656
+ return 'IPFS node URL cannot be empty' ;
657
+ }
658
+ try {
659
+ new URL ( value ) ;
660
+ return true ;
661
+ } catch {
662
+ return 'Please enter a valid URL' ;
663
+ }
664
+ } ,
665
+ result : value => {
666
+ ipfsNode = value ;
667
+ initDebugger . extend ( 'processInitForm' ) ( 'ipfs: %O' , value ) ;
668
+ return value ;
669
+ } ,
670
+ } ) ;
671
+
607
672
promptManager . addStep ( {
608
673
type : 'input' ,
609
674
name : 'source' ,
@@ -616,9 +681,16 @@ async function processInitForm(
616
681
isSubstreams ||
617
682
( ! protocolInstance . hasContract ( ) && ! isComposedSubgraph ) ,
618
683
initial : initContract ,
619
- validate : async ( value : string ) => {
684
+ validate : async ( value : string ) : Promise < string | boolean > => {
620
685
if ( isComposedSubgraph ) {
621
- return value . startsWith ( 'Qm' ) ? true : 'Subgraph deployment ID must start with Qm' ;
686
+ const ipfs = createIpfsClient ( ipfsNode ) ;
687
+ const manifestYaml = await loadManifestYaml ( ipfs , value ) ;
688
+ const { valid, error } = validateSubgraphNetworkMatch ( manifestYaml , network . id ) ;
689
+ if ( ! valid ) {
690
+ return error || 'Invalid subgraph network match' ;
691
+ }
692
+ startBlock ||= getMinStartBlock ( manifestYaml ) ?. toString ( ) ;
693
+ return true ;
622
694
}
623
695
if ( initFromExample !== undefined || ! protocolInstance . hasContract ( ) ) {
624
696
return true ;
@@ -668,6 +740,7 @@ async function processInitForm(
668
740
} else {
669
741
abiFromApi = initAbi ;
670
742
}
743
+
671
744
// If startBlock is not provided, try to fetch it from Etherscan API
672
745
if ( ! initStartBlock ) {
673
746
startBlock = await retryWithPrompt ( ( ) =>
@@ -699,19 +772,6 @@ async function processInitForm(
699
772
} ,
700
773
} ) ;
701
774
702
- promptManager . addStep ( {
703
- type : 'input' ,
704
- name : 'ipfs' ,
705
- message : `IPFS node to use for fetching subgraph manifest` ,
706
- initial : ipfsUrl ,
707
- skip : ( ) => ! isComposedSubgraph ,
708
- result : value => {
709
- ipfsNode = value ;
710
- initDebugger . extend ( 'processInitForm' ) ( 'ipfs: %O' , value ) ;
711
- return value ;
712
- } ,
713
- } ) ;
714
-
715
775
promptManager . addStep ( {
716
776
type : 'input' ,
717
777
name : 'spkg' ,
@@ -751,7 +811,7 @@ async function processInitForm(
751
811
isSubstreams ||
752
812
! ! initAbiPath ||
753
813
isComposedSubgraph ,
754
- validate : async ( value : string ) => {
814
+ validate : async ( value : string ) : Promise < string | boolean > => {
755
815
if (
756
816
initFromExample ||
757
817
abiFromApi ||
@@ -1199,6 +1259,14 @@ async function initSubgraphFromContract(
1199
1259
} ,
1200
1260
} ) ;
1201
1261
1262
+ // Validate network match first
1263
+ const manifestYaml = await loadManifestYaml ( ipfsClient , source ) ;
1264
+ const { valid, error } = validateSubgraphNetworkMatch ( manifestYaml , network ) ;
1265
+ if ( ! valid ) {
1266
+ throw new Error ( error || 'Invalid subgraph network match' ) ;
1267
+ }
1268
+
1269
+ startBlock ||= getMinStartBlock ( manifestYaml ) ?. toString ( ) ;
1202
1270
const schemaString = await loadSubgraphSchemaFromIPFS ( ipfsClient , source ) ;
1203
1271
const schema = await Schema . loadFromString ( schemaString ) ;
1204
1272
entities = schema . getEntityNames ( ) ;
@@ -1208,8 +1276,9 @@ async function initSubgraphFromContract(
1208
1276
}
1209
1277
1210
1278
if (
1211
- ! protocolInstance . isComposedSubgraph ( ) &&
1279
+ ! isComposedSubgraph &&
1212
1280
protocolInstance . hasABIs ( ) &&
1281
+ abi && // Add check for abi existence
1213
1282
( abiEvents ( abi ) . size === 0 ||
1214
1283
// @ts -expect-error TODO: the abiEvents result is expected to be a List, how's it an array?
1215
1284
abiEvents ( abi ) . length === 0 )
@@ -1224,6 +1293,12 @@ async function initSubgraphFromContract(
1224
1293
`Failed to create subgraph scaffold` ,
1225
1294
`Warnings while creating subgraph scaffold` ,
1226
1295
async spinner => {
1296
+ initDebugger ( 'Generating scaffold with ABI:' , abi ) ;
1297
+ initDebugger ( 'ABI data:' , abi ?. data ) ;
1298
+ if ( abi ) {
1299
+ initDebugger ( 'ABI events:' , abiEvents ( abi ) ) ;
1300
+ }
1301
+
1227
1302
const scaffold = await generateScaffold (
1228
1303
{
1229
1304
protocolInstance,
@@ -1280,7 +1355,7 @@ async function initSubgraphFromContract(
1280
1355
this . exit ( 1 ) ;
1281
1356
}
1282
1357
1283
- while ( addContract ) {
1358
+ while ( addContract && ! isComposedSubgraph ) {
1284
1359
addContract = await addAnotherContract
1285
1360
. bind ( this ) ( {
1286
1361
protocolInstance,
0 commit comments