Skip to content
This repository has been archived by the owner on Jan 24, 2024. It is now read-only.

Commit

Permalink
feat(studio): Add Viem multicall dataloader and CLI for contract fact…
Browse files Browse the repository at this point in the history
…ory (#3021)
  • Loading branch information
immasandwich committed Nov 8, 2023
1 parent 1146d68 commit 299f02d
Show file tree
Hide file tree
Showing 91 changed files with 8,585 additions and 4,677 deletions.
6 changes: 4 additions & 2 deletions cli/commands/create-app.ts
Expand Up @@ -5,7 +5,8 @@ import { generateAppModule } from '../generators/generate-app-module';
import { promptAppId, promptAppName, promptAppNetworks } from '../prompts';
import { strings } from '../strings';

import { generateContractFactory } from './generate-contract-factory';
import { generateEthersContractFactory } from './generate-contract-factory/generate-ethers-contract-factory';
import { generateViemContractFactory } from './generate-contract-factory/generate-viem-contract-factory';

export default class CreateApp extends Command {
static description = 'Creates the starting point for an app integration';
Expand All @@ -27,7 +28,8 @@ export default class CreateApp extends Command {
}

await generateAppModule(appId);
await generateContractFactory(`./src/apps/${appId}`);
await generateEthersContractFactory(`./src/apps/${appId}`);
await generateViemContractFactory(`./src/apps/${appId}`);

this.log(`Done! Your app ${appName} has been generated`);
}
Expand Down
192 changes: 0 additions & 192 deletions cli/commands/generate-contract-factory.ts

This file was deleted.

@@ -0,0 +1,86 @@
/* eslint no-console: 0 */
import path from 'path';

import { writeFileSync } from 'fs-extra';
import { camelCase, sortBy, upperFirst } from 'lodash';

import { getAbis } from './get-abis';

export const generateEthersContractFactory = async (location: string) => {
const abis = sortBy(await getAbis(location));
const factoryName = upperFirst(camelCase(path.basename(location)));
const isRoot = path.basename(location) === 'contract';
const factoryFullName = isRoot ? 'ContractFactory' : `${factoryName}ContractFactory`;

const renderer = {
ethers: async () => {
const imports = abis.flatMap(abi => {
const typeName = upperFirst(camelCase(abi));
return [`import { ${typeName}__factory } from './ethers';`, `import type { ${typeName} } from './ethers';`];
});

const factoryMethods = abis.map(abi => {
const methodName = camelCase(abi);
const typeName = upperFirst(methodName);
return `${methodName}({address, network}: ContractOpts) { return ${typeName}__factory.connect(address, ${
isRoot ? 'this.networkProviderResolver(network)' : 'this.appToolkit.getNetworkProvider(network)'
}); }`;
});

const interfaceRows = abis.map(abi => {
const methodName = camelCase(abi);
const typeName = upperFirst(methodName);
return `${methodName}(opts: ContractOpts): ${typeName};`;
});

const reexports = abis.map(abi => {
const typeName = upperFirst(camelCase(abi));
return [`export type { ${typeName} } from './ethers'`];
});

writeFileSync(
path.join(location, `/contracts/ethers.contract-factory.ts`),
`
import { Injectable } from '@nestjs/common';
import { Inject } from '@nestjs/common';
import { StaticJsonRpcProvider } from '@ethersproject/providers';
import { IAppToolkit, APP_TOOLKIT } from '~app-toolkit/app-toolkit.interface';
import { NetworkProviderService } from '~network-provider/network-provider.service';
import { Network } from '~types/network.interface';
${imports.join('\n')}
${isRoot ? '' : "import { ContractFactory } from '~contract/contracts'"}
type ContractOpts = {address: string, network: Network};
${!isRoot ? '' : 'type NetworkProviderResolver = (network: Network) => StaticJsonRpcProvider;'}
${
isRoot
? `
export interface IContractFactory {
${interfaceRows.join('\n')}
}
`
: ''
}
@Injectable()
export class ${factoryFullName} ${isRoot ? 'implements IContractFactory' : 'extends ContractFactory'} {
constructor(${
isRoot
? 'protected readonly networkProviderResolver: NetworkProviderResolver'
: '@Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit'
}) { ${isRoot ? '' : 'super((network: Network) => appToolkit.getNetworkProvider(network));'} }
${factoryMethods.join('\n')}
}
${reexports.join('\n')}
`,
);
},
};

await renderer.ethers();
};
31 changes: 31 additions & 0 deletions cli/commands/generate-contract-factory/generate-ethers-contract.ts
@@ -0,0 +1,31 @@
/* eslint no-console: 0 */
import fs from 'fs';
import path from 'path';
import util from 'util';

import { glob, runTypeChain } from 'typechain';

const mkdir = util.promisify(fs.mkdir);
const rmdir = util.promisify(fs.rm);
const exists = util.promisify(fs.exists);

export const generateEthersContract = async (location: string) => {
const providerDir = path.join(location, `/contracts/ethers`);
const providerDirExists = await exists(providerDir);
if (providerDirExists) {
await rmdir(providerDir, { recursive: true });
await mkdir(providerDir, { recursive: true });
}

const cwd = process.cwd();
const allFiles = glob(cwd, [path.join(location, '/contracts/abis/*.json')]);
if (!allFiles.length) return;

await runTypeChain({
cwd,
filesToProcess: allFiles,
allFiles,
outDir: providerDir,
target: 'ethers-v5',
});
};
24 changes: 24 additions & 0 deletions cli/commands/generate-contract-factory/generate-index.ts
@@ -0,0 +1,24 @@
/* eslint no-console: 0 */
import fs, { writeFileSync } from 'fs';
import path from 'path';
import util from 'util';

const exists = util.promisify(fs.exists);

export const generateIndex = async (location: string) => {
let content = `
/* Autogenerated file. Do not edit manually. */
/* tslint:disable */
/* eslint-disable */
`;

if (await exists(path.join(location, `/contracts/ethers.contract-factory.ts`))) {
content += `export * from './ethers.contract-factory';\n`;
}

if (await exists(path.join(location, `/contracts/viem.contract-factory.ts`))) {
content += `export * from './viem.contract-factory';\n`;
}

writeFileSync(path.join(location, `/contracts/index.ts`), content);
};

0 comments on commit 299f02d

Please sign in to comment.