Skip to content

Commit ab0d3b3

Browse files
committed
Template contracts
1 parent 37de3e8 commit ab0d3b3

File tree

8 files changed

+145
-56
lines changed

8 files changed

+145
-56
lines changed

components/SubgraphEditor/SubgraphConfig/SelectedContract/SelectedContract.tsx

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useRef, useState } from 'react'
22
import styled from 'styled-components'
3-
import { Plus, Trash2 } from 'lucide-react'
3+
import { Files, Plus, Trash2 } from 'lucide-react'
4+
import ReactTooltip from 'react-tooltip'
45

56
import { Contract, ContractEvent } from 'hooks/local-subgraphs'
67
import { EventRow } from './EventRow'
@@ -30,15 +31,15 @@ const Header = styled.div`
3031
display: flex;
3132
align-items: center;
3233
gap: 8px;
33-
34-
.delete-link {
35-
&:hover {
36-
cursor: pointer;
37-
}
38-
}
3934
}
4035
`
4136

37+
const IconBtn = styled.button<{ active?: boolean }>`
38+
color: ${({ active }) => (active ? '#ffffff' : '#bbbbbb')};
39+
border: none;
40+
background: transparent;
41+
`
42+
4243
const StatusContainer = styled.div`
4344
display: flex;
4445
justify-content: space-between;
@@ -115,7 +116,7 @@ interface SelectedContractProps {
115116

116117
export const SelectedContract = (props: SelectedContractProps) => {
117118
const {
118-
contract: { addresses, name, source, errorMessage, abi, startBlocks, events },
119+
contract: { addresses, name, source, errorMessage, abi, startBlocks, events, isTemplate },
119120
createMappingFn,
120121
deleteContract,
121122
fnExtractionLoading,
@@ -231,13 +232,28 @@ export const SelectedContract = (props: SelectedContractProps) => {
231232
onChange={name => updateContract({ name })}
232233
/>
233234

234-
<Trash2 className="delete-link" size={16} onClick={() => deleteContract()} />
235+
<IconBtn data-tip="Remove contract" onClick={deleteContract}>
236+
<Trash2 size={16} />
237+
</IconBtn>
238+
<IconBtn
239+
data-tip="Data Source Template"
240+
active={isTemplate}
241+
onClick={() => updateContract({ isTemplate: !isTemplate })}
242+
disabled={metadataLoading}
243+
>
244+
<Files size={16} />
245+
</IconBtn>
246+
<ReactTooltip />
235247
</div>
236-
<span className="address">{addresses[CHAIN_ID]}</span>
237-
{metadataLoading ? <span>Fetching contract metadata...</span> : null}
238-
{startBlocks[CHAIN_ID] ? (
239-
<span className="address">Deployed on block {startBlocks[CHAIN_ID]}</span>
240-
) : null}
248+
{!isTemplate && (
249+
<>
250+
<span className="address">{addresses[CHAIN_ID]}</span>
251+
{metadataLoading ? <span>Fetching contract metadata...</span> : null}
252+
{startBlocks[CHAIN_ID] ? (
253+
<span className="address">Deployed on block {startBlocks[CHAIN_ID]}</span>
254+
) : null}
255+
</>
256+
)}
241257
<StatusContainer>
242258
<span className="status-message">
243259
{errorMessage
@@ -322,10 +338,12 @@ export const SelectedContract = (props: SelectedContractProps) => {
322338
<ActionButton
323339
disabled={!contractHasEvents}
324340
onClick={() => setNewEvent({ show: true, signature: '' })}
325-
{...(!contractHasEvents && {
326-
disabled: true,
327-
title: 'Contract has no events defined',
328-
})}
341+
{...(contractHasEvents
342+
? {}
343+
: {
344+
disabled: true,
345+
title: 'Contract has no events defined',
346+
})}
329347
>
330348
<Plus size={12} style={{ marginRight: 4 }} />
331349
New

components/SubgraphEditor/SubgraphConfig/SubgraphConfig.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { EDITOR_TYPES, useEditorState } from 'hooks/editor-state'
88
import { SelectedContract } from './SelectedContract'
99
import { Dropdown } from '../../atoms'
1010
import { addImport } from 'utils/source-code-utils'
11-
import { generateContractFile, generateSchemaFile } from 'utils/graph-file-generator'
11+
import { generateLibrariesForSubgraph } from 'utils/graph-file-generator'
1212
import { compileAs } from 'utils/deploy-subgraph'
1313

1414
const Root = styled.div`
@@ -101,6 +101,7 @@ export const SubgraphConfig = (props: SubgraphConfigProps) => {
101101
if (!alreadySelected) {
102102
const newContract: Contract = {
103103
name: '',
104+
isTemplate: false,
104105
addresses: { [CHAIN_ID]: address },
105106
abi: null,
106107
startBlocks: {},
@@ -122,14 +123,7 @@ export const SubgraphConfig = (props: SubgraphConfigProps) => {
122123
setFnExtractionLoading(true)
123124
const { loadAsBytecode } = await import('utils/as-compiler')
124125

125-
const libraries: { [name: string]: string } = {}
126-
127-
for (const contract of subgraph.contracts) {
128-
const code = await generateContractFile(contract.name, contract.abi)
129-
libraries[`contracts/${contract.name}.ts`] = code
130-
}
131-
132-
libraries['schema/index.ts'] = await generateSchemaFile(subgraph.schema)
126+
const libraries = await generateLibrariesForSubgraph(subgraph)
133127

134128
try {
135129
const bytecode = await compileAs(subgraph.mappings[DEFAULT_MAPPING], libraries)

components/SubgraphEditor/atoms/EditableText.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'
33
import styled, { css } from 'styled-components'
44

55
const commonStyles = css`
6+
color: var(--color-white);
7+
font-size: 14px;
68
padding: 1px 2px;
79
font-family: 'Inter', sans-serif;
810
`

generated/graph-ts-sdk.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
declare module '@graphprotocol/graph-ts' {
22
namespace ethereum {
3+
export declare class Value {}
4+
35
export declare class Block {
46
public hash: Bytes
57
public parentHash: Bytes

hooks/local-subgraphs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface ContractEvent {
1919

2020
export interface Contract {
2121
name: string
22+
isTemplate: boolean
2223
addresses: { [chain: string]: string }
2324
startBlocks: { [chain: string]: number }
2425
abi: any

hooks/useGeneratedFiles.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { useEffect, useState } from 'react'
2-
import { generateContractFile, generateSchemaFile } from 'utils/graph-file-generator'
2+
import {
3+
generateContractFile,
4+
generateSchemaFile,
5+
generateTemplateFile,
6+
} from 'utils/graph-file-generator'
37
import { SubgraphData } from './local-subgraphs'
48
// @ts-ignore
59
import assemblyscriptGlobals from '!raw-loader!assemblyscript/std/assembly/index.d.ts'
@@ -19,6 +23,11 @@ export const useGeneratedFiles = (subgraph: SubgraphData | null) => {
1923
try {
2024
const content = await generateContractFile(contract.name, contract.abi)
2125
_files.push({ content, filePath: `file:///contracts/${contract.name}.ts` })
26+
27+
if (contract.isTemplate) {
28+
const template = await generateTemplateFile(contract.name)
29+
_files.push({ content: template, filePath: `file:///templates/${contract.name}.ts` })
30+
}
2231
} catch (e) {
2332
console.error(`Error generating file for ${contract.name}: ${e.message}`)
2433
}

utils/deploy-subgraph.ts

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ContractEvent, DEFAULT_MAPPING, SubgraphData } from 'hooks/local-subgraphs'
2-
import { generateContractFile, generateSchemaFile } from './graph-file-generator'
2+
import { generateLibrariesForSubgraph } from './graph-file-generator'
33
import Hash from 'ipfs-only-hash'
44

55
export enum STATUS {
@@ -136,14 +136,7 @@ export async function prepareSubgraphDeploymentFiles(subgraph: SubgraphData) {
136136
throw new Error(`Couldn't find event ${signature} in ABI`)
137137
}
138138

139-
const libraries: { [name: string]: string } = {}
140-
141-
for (const contract of subgraph.contracts) {
142-
const code = await generateContractFile(contract.name, contract.abi)
143-
libraries[`contracts/${contract.name}.ts`] = code
144-
}
145-
146-
libraries['schema/index.ts'] = await generateSchemaFile(subgraph.schema)
139+
const libraries = await generateLibrariesForSubgraph(subgraph)
147140

148141
const compiled = await compileAs(subgraph.mappings[DEFAULT_MAPPING], libraries)
149142

@@ -166,6 +159,7 @@ export async function prepareSubgraphDeploymentFiles(subgraph: SubgraphData) {
166159
})
167160

168161
const dataSources: any[] = []
162+
const templates: any[] = []
169163

170164
// Currently, all ABIs are available to all data sources, so we generate
171165
// one single array that's added to all sources
@@ -189,14 +183,12 @@ export async function prepareSubgraphDeploymentFiles(subgraph: SubgraphData) {
189183
cid,
190184
})
191185

192-
dataSources.push({
186+
const contractManifest: any = {
193187
kind: 'ethereum/contract',
194188
name: contract.name,
195189
network: 'mainnet',
196190
source: {
197191
abi: contract.name,
198-
address: contract.addresses['1'],
199-
startBlock: contract.startBlocks['1'],
200192
},
201193
mapping: {
202194
abis,
@@ -213,7 +205,15 @@ export async function prepareSubgraphDeploymentFiles(subgraph: SubgraphData) {
213205
kind: 'ethereum/events',
214206
language: 'wasm/assemblyscript',
215207
},
216-
})
208+
}
209+
210+
if (contract.isTemplate) {
211+
templates.push(contractManifest)
212+
} else {
213+
contractManifest.source.address = contract.addresses['1']
214+
contractManifest.source.startBlock = contract.startBlocks['1']
215+
dataSources.push(contractManifest)
216+
}
217217
}
218218

219219
const schemaCid = await getCID(subgraph.schema)
@@ -225,23 +225,29 @@ export async function prepareSubgraphDeploymentFiles(subgraph: SubgraphData) {
225225
cid: schemaCid,
226226
})
227227

228-
const manifestString = yaml.dump({
229-
specVersion: '0.0.5',
230-
description: 'Test description',
231-
dataSources,
232-
schema: {
233-
file: {
234-
'/': `/ipfs/${schemaCid}`,
228+
const manifestString = yaml.dump(
229+
{
230+
specVersion: '0.0.5',
231+
description: 'Test description',
232+
dataSources,
233+
templates,
234+
schema: {
235+
file: {
236+
'/': `/ipfs/${schemaCid}`,
237+
},
235238
},
239+
sourceCode: [
240+
{
241+
name: 'mapping.ts',
242+
file: `/ipfs/${mappingCid}`,
243+
source: `/ipfs/${sourceCid}`,
244+
},
245+
],
236246
},
237-
sourceCode: [
238-
{
239-
name: 'mapping.ts',
240-
file: `/ipfs/${mappingCid}`,
241-
source: `/ipfs/${sourceCid}`,
242-
},
243-
],
244-
})
247+
{
248+
noRefs: true,
249+
}
250+
)
245251

246252
files.push({
247253
title: 'Manifest',

utils/graph-file-generator.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
1+
import { SubgraphData } from 'hooks/local-subgraphs'
12
import immutable from 'immutable'
23

4+
export async function generateLibrariesForSubgraph(
5+
subgraph: SubgraphData,
6+
{ prefix = '', failSilently = false } = {}
7+
) {
8+
const libraries: { [name: string]: string } = {}
9+
10+
for (const contract of subgraph.contracts) {
11+
try {
12+
const code = await generateContractFile(contract.name, contract.abi)
13+
libraries[`${prefix}contracts/${contract.name}.ts`] = code
14+
console.log(contract)
15+
if (contract.isTemplate) {
16+
const template = await generateTemplateFile(contract.name)
17+
libraries[`${prefix}templates/${contract.name}.ts`] = template
18+
}
19+
} catch (e: any) {
20+
if (failSilently) {
21+
console.error(`Error generating file for ${contract.name}: ${e.message}`)
22+
} else {
23+
throw e
24+
}
25+
}
26+
}
27+
28+
try {
29+
libraries[`${prefix}schema/index.ts`] = await generateSchemaFile(subgraph.schema)
30+
} catch (e: any) {
31+
if (failSilently) {
32+
console.error(`Error generating schema: ${e.message}`)
33+
} else {
34+
throw e
35+
}
36+
}
37+
38+
return libraries
39+
}
40+
341
export async function generateContractFile(name: string, abi: any) {
442
// @ts-ignore
543
const { default: ABI } = await import('@graphprotocol/graph-cli/src/protocols/ethereum/abi')
@@ -11,6 +49,25 @@ export async function generateContractFile(name: string, abi: any) {
1149
return [...codegen.generateModuleImports(), ...codegen.generateTypes()].join('\n')
1250
}
1351

52+
export async function generateTemplateFile(name: string) {
53+
const { default: Template } = await import(
54+
// @ts-ignore
55+
'@graphprotocol/graph-cli/src/protocols/ethereum/codegen/template'
56+
)
57+
const { default: DataSourceTemplateCodeGenerator } = await import(
58+
// @ts-ignore
59+
'@graphprotocol/graph-cli/src/codegen/template'
60+
)
61+
62+
const templateData = { get: () => name }
63+
const template = new Template(templateData)
64+
const codegen = new DataSourceTemplateCodeGenerator(templateData, {
65+
getTemplateCodeGen: () => template,
66+
})
67+
68+
return [...codegen.generateModuleImports(), ...codegen.generateTypes()].join('\n')
69+
}
70+
1471
export async function generateSchemaFile(schemaCode: string) {
1572
const [gql, { default: SchemaCodeGenerator }] = await Promise.all([
1673
import('graphql/language'),

0 commit comments

Comments
 (0)