Skip to content

Commit

Permalink
Support config.js, introduce context type for global state, and expor…
Browse files Browse the repository at this point in the history
…t config types (#33)

* Fix conflicts

* Start refactoring client

* Change fs to async

* Introduce shared context and rework config logic

* Check network symbols with Zod and add tests

* Support config.js and cover config logic with tests

* Add .vscode directory with tasks configuration

* Make EthSdkCtx readonly

* Address review comments

* Make Address Opaque again

* Ignore test files in tsconfig.build.json

* Support configs written in TypeScript when ts-node is installed

* Add changeset

* Describe new config in README.md
  • Loading branch information
hasparus authored Nov 2, 2021
1 parent 6c0ae88 commit 3e32900
Show file tree
Hide file tree
Showing 60 changed files with 894 additions and 385 deletions.
44 changes: 44 additions & 0 deletions .changeset/heavy-buses-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
'@dethcrypto/eth-sdk': minor
---

**Breaking Changes:**

1. Config files can now be named `config` or `eth-sdk.config` instead of `contracts`. Supported extensions are `.js`, `.ts`, `.cjs` and `.json`.

```ts
import { defineConfig } from '@dethcrypto/eth-sdk'

export default defineConfig({
contracts: {
mainnet: {
dai: '0x6b175474e89094c44da98b954eedeac495271d0f',
},
},
outputPath: './eth-sdk/client'
});
```

2. `--out` flag in CLI is no longer supported in favor of `config.outputPath`.

**How to migrate?**

Rename your `contracts.json` file to `config.json` and paste it's contents under "contracts" property.

Before:

```json
{
"mainnet": { /* your contracts */ }
}
```

After:

```json
{
"contracts": {
"mainnet": { /* your contracts */ }
}
}
```
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.md
2 changes: 1 addition & 1 deletion .mocharc.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ module.exports = {
require: ['ts-node/register/transpile-only', 'earljs/mocha'],
extension: ['ts'],
watchExtensions: ['ts'],
spec: ['test/**/*.test.ts'],
spec: ['{test,src}/**/*.test.ts'],
timeout: 5000,
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
33 changes: 33 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "eth-sdk: typecheck",
"type": "shell",
"command": "yarn tsc -P ./packages/eth-sdk/tsconfig.json --noEmit --incremental false --composite false",
"problemMatcher": [
"$tsc"
]
},
{
"label": "eth-sdk: lint:fix",
"type": "npm",
"script": "lint:fix",
"path": "packages/eth-sdk/",
"options": {
"cwd": "packages/eth-sdk/"
},
"problemMatcher": [
"$eslint-stylish"
]
},
{
"label": "eth-sdk: check",
"dependsOn": [
"eth-sdk: typecheck",
"eth-sdk: lint:fix"
],
"problemMatcher": []
},
]
}
14 changes: 14 additions & 0 deletions examples/hardhat/eth-sdk/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"contracts": {
"mainnet": {
"tokens": {
"comp": "0xc00e94cb662c3520282e6f5717214004a7f26888"
},
"compound": {
"comptroller": "0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B",
"comptrollerImpl": "0x374ABb8cE19A73f2c4EFAd642bda76c797f19233",
"treasury": "0x2775b1c75658be0f640272ccb8c72ac986009e38"
}
}
}
}
12 changes: 0 additions & 12 deletions examples/hardhat/eth-sdk/contracts.json

This file was deleted.

2 changes: 1 addition & 1 deletion examples/hardhat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
"@nomiclabs/hardhat-ethers": "^2.0.2",
"ethers": "^5.4.7",
"hardhat": "^2.6.5",
"typescript": "^4.4.3"
"typescript": "^4.4.4"
}
}
5 changes: 0 additions & 5 deletions examples/vite-react/eth-sdk/contracts.json

This file was deleted.

11 changes: 11 additions & 0 deletions examples/vite-react/eth-sdk/eth-sdk.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { EthSdkConfig } from '@dethcrypto/eth-sdk'

const config: EthSdkConfig = {
contracts: {
mainnet: {
dai: '0x6b175474e89094c44da98b954eedeac495271d0f',
},
},
}

export default config
3 changes: 1 addition & 2 deletions examples/vite-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@
"@types/react-dom": "^17.0.0",
"@vitejs/plugin-react": "^1.0.0",
"concurrently": "^6.3.0",
"ts-node": "^10.4.0",
"typescript": "^4.4.0",
"typescript": "^4.4.4",
"vite": "^2.6.4"
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"mocha": "^9.0.1",
"prettier": "^2.4.1",
"ts-node": "^10.0.0",
"typescript": "^4.3.4",
"typescript": "^4.4.4",
"wsrun": "^5.2.4"
}
}
26 changes: 15 additions & 11 deletions packages/eth-sdk/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img src="./docs/logo.png?raw=true" width="100" alt="eth-sdk">
<img src="https://github.com/dethcrypto/eth-sdk/blob/master/docs/logo.png?raw=true" width="100" alt="" role="presentation">
<h3 align="center">eth-sdk</h3>
<p align="center">Generate type-safe, lightweight SDK for your Ethereum smart contracts</p>
<p align="center">The quickest and easiest way to interact with Ethereum</p>
Expand Down Expand Up @@ -38,18 +38,22 @@ away. The SDK is an object consisting of ethers.js contracts initialized with AB
generated via TypeChain.

The first step is to create a config file specifying contracts that we wish to interact with. Default path to this file
is `eth-sdk/contracts.json`:

```json
{
"mainnet": {
"dai": "0x6B175474E89094C44Da98b954EedeAC495271d0F"
}
}
is `eth-sdk/config.ts`, but we also support `.json`, `.js` and `.cjs` extensions and `eth-sdk.config` base name.

```ts
import { defineConfig } from '@dethcrypto/eth-sdk'

export default defineConfig({
contracts: {
mainnet: {
dai: '0x6b175474e89094c44da98b954eedeac495271d0f',
},
},
})
```

The top level key is a network identifier, `eth-sdk` needs it to query ABI information automatically. Following are
key-value pairs of contract names and addresses. These can be arbitrarily nested.
The key directly under `"contracts"` is a network identifier, `eth-sdk` needs it to query ABI information automatically.
Following are key-value pairs of contract names and addresses. These can be deeply nested.

Now you're ready to run `yarn eth-sdk`. Few things will happen under the hood:

Expand Down
14 changes: 9 additions & 5 deletions packages/eth-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@
"bin": {
"eth-sdk": "dist/cli.js"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist/**/*",
"static/**/*"
],
"scripts": {
"format": "prettier --config ../../.prettierrc --ignore-path ../../.prettierignore --check \"./**/*.ts\"",
"format:fix": "prettier --config ../../.prettierrc --ignore-path ../../.prettierignore --write \"./**/*.ts\"",
"lint": "eslint --ext .ts src test",
"lint": "eslint --ext .ts src",
"lint:fix": "yarn lint --fix",
"typecheck": "tsc --noEmit --incremental false --composite false",
"clean": "rm -rf dist && rm -f tsconfig.build.tsbuildinfo",
Expand All @@ -46,22 +48,22 @@
"lodash": "^4.17.21",
"mkdirp": "^1.0.4",
"ora": "^5.4.1",
"tmp": "^0.2.1",
"tmp-promise": "^3.0.3",
"typechain": "^5.1.1",
"zod": "^3.2.0"
"zod": "^3.11.5"
},
"peerDependencies": {
"ethers": "^5.3.1"
},
"devDependencies": {
"ts-essentials": "^7.0.2",
"@types/debug": "^4.1.6",
"@types/fs-extra": "^9.0.11",
"@types/glob": "^7.1.3",
"@types/lodash": "^4.14.170",
"@types/mkdirp": "^1.0.1",
"@types/mocha": "^8.2.2",
"@types/node": "^14.14.10",
"@types/proxyquire": "^1.3.28",
"@types/tmp": "^0.2.0",
"@typescript-eslint/eslint-plugin": "^4.28.0",
"@typescript-eslint/parser": "^4.28.0",
Expand All @@ -75,7 +77,9 @@
"eslint-plugin-unused-imports": "^1.1.1",
"mocha": "^9.0.1",
"prettier": "^2.3.1",
"proxyquire": "^2.1.3",
"ts-essentials": "^7.0.2",
"ts-node": "^10.0.0",
"typescript": "^4.3.4"
"typescript": "^4.4.4"
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import got from 'got'

import { Address } from '../../sdk-def'
import { Address } from '../../config'
import { symbolToNetworkId } from '../networks'
import { EtherscanURLs, networkIDtoEndpoints } from './urls'

Expand Down
19 changes: 10 additions & 9 deletions packages/eth-sdk/src/abi-management/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import debug from 'debug'
import { dirname, join } from 'path'

import { Fs } from '../helpers/fs'
import { traverseSdkDefinition } from '../helpers/traverse'
import { SdkDefinition } from '../sdk-def'
import { Address } from '../sdk-def'
import { Address } from '../config'
import { traverseContractsMap } from '../config/traverse'
import { EthSdkCtx } from '../types'
import { getABIFromEtherscan } from './etherscan/getAbiFromEtherscan'
import { GetAbi } from './types'
const d = debug('@dethcrypto/eth-sdk:abi')

export async function gatherABIs(def: SdkDefinition, outputRoot: string, fs: Fs, getAbi: GetAbi = getABIFromEtherscan) {
await traverseSdkDefinition(def, async (network: string, key: string[], address: Address) => {
const fullAbiPath = join(outputRoot, 'abis', network, ...key) + '.json'
export async function gatherABIs(ctx: EthSdkCtx, getAbi: GetAbi = getABIFromEtherscan) {
const { config, fs } = ctx

await traverseContractsMap(config.contracts, async (network: string, key: string[], address: Address) => {
const fullAbiPath = join(config.outputPath, 'abis', network, ...key) + '.json'
d(`Getting ABI for ${key.join('.')}`)

if (!fs.exists(fullAbiPath)) {
d('ABI doesnt exist already. Querying etherscan')
const abi = await getAbi(network, address)
fs.ensureDir(dirname(fullAbiPath))
fs.write(fullAbiPath, JSON.stringify(abi))
await fs.ensureDir(dirname(fullAbiPath))
await fs.write(fullAbiPath, JSON.stringify(abi))
}
})
}
9 changes: 5 additions & 4 deletions packages/eth-sdk/src/abi-management/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ export enum NetworkID {
ARBITRUM_TESTNET = 421611,
}

export const networkIDtoSymbol: {
[networkID in NetworkID]: string
} = {
export const networkIDtoSymbol = {
[NetworkID.MAINNET]: 'mainnet',
[NetworkID.ROPSTEN]: 'ropsten',
[NetworkID.RINKEBY]: 'rinkeby',
Expand All @@ -48,5 +46,8 @@ export const networkIDtoSymbol: {
[NetworkID.POLYGON_MUMBAI]: 'polygonMumbai',
[NetworkID.ARBITRUM_ONE]: 'arbitrumOne',
[NetworkID.ARBITRUM_TESTNET]: 'arbitrumTestnet',
}
} as const

export type NetworkSymbol = typeof networkIDtoSymbol[keyof typeof networkIDtoSymbol]

export const symbolToNetworkId: SafeDictionary<NetworkID, string> = invert(networkIDtoSymbol) as any
2 changes: 1 addition & 1 deletion packages/eth-sdk/src/abi-management/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { Address } from '../sdk-def'
import { Address } from '../config'

export type GetAbi = (network: string, address: Address) => Promise<Object>
40 changes: 27 additions & 13 deletions packages/eth-sdk/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,44 @@ import ora from 'ora'
import { relative } from 'path'

import { gatherABIs } from './abi-management'
import { generateClient } from './client'
import { realFs } from './helpers/fs'
import { generateSdk } from './client'
import { findConfigFile, readConfig } from './config'
import { parseArgs } from './parseArgs'
import { loadSdkDefinition } from './sdk-def/loadSdkDef'
import { formatError } from './peripherals/formatError'
import { realFs } from './peripherals/fs'
import { EthSdkCtx } from './types'

const d = debug('@dethcrypto/eth-sdk:cli')

export async function main() {
const cwd = process.cwd()
const args = parseArgs({ argv: process.argv, cwd })
const fs = realFs

d('Parsed args', args)
let ctx: EthSdkCtx
try {
const cliArgs = parseArgs({ argv: process.argv, cwd })

const fs = realFs

d('Parsed args', cliArgs)

const configPath = findConfigFile(cliArgs, fs)
const config = await readConfig(configPath, require)

const sdkDef = loadSdkDefinition(args, fs)
ctx = { cliArgs, config, fs }
} catch (error) {
console.error(formatError(error))
process.exit(1)
}

console.log(`Loaded sdk definition from ${chalk.green(args.workingDirPath)}`)
console.log(`Loaded sdk definition from ${chalk.green(ctx.cliArgs.workingDirPath)}`)

await spin(gatherABIs(sdkDef, args.workingDirPath, fs), 'Getting ABIs')
await spin(generateClient(sdkDef, args.workingDirPath, args.outputRootPath), 'Generating client')
console.log(`SDK generated to: ./${relative(cwd, args.outputRootPath)}`)
await spin('Getting ABIs', gatherABIs(ctx))
await spin('Generating client', generateSdk(ctx))
console.log(`SDK generated to: ${relative(cwd, ctx.config.outputPath)}`)
}

async function spin<T>(promise: Promise<T>, name: string): Promise<T> {
ora.promise(promise, { text: name, spinner: 'dots3', color: 'magenta' })
async function spin<T>(text: string, promise: Promise<T>): Promise<T> {
ora.promise(promise, { text, spinner: 'dots3', color: 'magenta' })
return await promise
}

Expand Down
Loading

0 comments on commit 3e32900

Please sign in to comment.