Skip to content
/ harsta Public

harsta is a contract development tool based on Hardhat, designed to streamline the development, testing, and referencing of contracts, addresses, ABIs, and contract instances.

License

Notifications You must be signed in to change notification settings

hairyf/harsta

Repository files navigation

harsta

I have been searching for a way to integrate Hardhat with other frameworks (such as nextjs/vue), but unfortunately, I have not found one. Therefore, I plan to implement it myself.

harsta is a contract development tool based on Hardhat, designed to streamline the development, testing, and referencing of contracts, addresses, ABIs, and contract instances.

harsta simplifies the configuration files for end-to-end development and contract writing. You only need to write one configuration that can be referenced anywhere.

image

Installation

You can quickly create a project like Hardhat using the command pnpm harsta create, or you can install it separately in any project:

$ pnpm harsta create

# OR

$ pnpm install hardhat harsta @harsta/client

# OR

$ pnpm install hardhat harsta

Note: @harsta/client is optional. If installed, harsta compile will automatically be bundled into @harsta/client.

Directory

  • contracts/, test/

The location of contract source files and tests, similar to Hardhat.

  • config/addresses.ts

This file contains all contract addresses used in the project. You can customize this file, and it will be automatically updated after contract updates/deployments.

export default {
  [chainIdNumber]: { [contractName]: '0x...' },
  // or the contract address currently used in your project
  0: { USDT: '0x...' }
}
  • config/fragments/*.json

Additional third-party contracts used, which will be compiled into contract instances.

Quick Start

harsta has three key commands:

  harsta compile  Compile and output the directory
  harsta test     Run integration tests
  harsta deploy   Deploy and save deployments

To compile your contracts in contracts/:

$ pnpm harsta compile

If @harsta/client is not installed, harsta will default output to dist. You can change the output directory with the --output option.

After compilation, if you install @harsta/client, you can directly reference @harsta/client as follows:

import {
  addresses,
  chains,
  contracts,
  defaultChain,
  interfaces,
} from '@harsta/client'

// Automatically import contract addresses from the default chain
const lock = contracts.Lock.resolve()
const time = await lock.unlockTime()

// Use other addresses through attach
const lock2 = contracts.Lock.attach(addresses[chains.ethereum.id].Lock)

// Connect to a contract by passing a chain
const lock3 = contracts.Lock.resolve(chains.ethereum, '0x...')

Send and write contract:

import { contracts, updateRunner } from '@harsta/client'
// Update the default runner (singer) to support transactions
import { Wallet } from 'ethers'

const wallet = new Wallet('...')
updateRunner(wallet)

const lock = contracts.Lock.resolve()
const transaction = await lock.withdraw()

Config

harsta's config is similar to Hardhat but more streamlined and simple, with comprehensive chain config.

import { defineConfig } from 'harsta'
import 'dotenv/config'

const config = defineConfig({
  solidity: '0.8.20',
  defaultNetwork: 'geneva',
  namedAccounts: {/* ... */},
  networks: {
    ethereum: {
      name: 'Ethereum',
      rpc: '...',
      id: 0,
      icon: '',
      currency: {
        decimals: 18,
        name: 'ETH',
        symbol: 'ETH'
      },
      explorer: {/* ... */},
      // Optional, if not written, it will not be accepted by Hardhat
      deploy: {
        accounts: [/* account #0 | account #1 */],
        saveDeployments: true,
        allowUnlimitedContractSize: true,
        gas: 'auto',
        gasPrice: 'auto',
      },
      verify: {
        uri: '...',
        key: '...'
      }
    }
  },
  // ...
})

Deployments

After writing your contracts, you need to define the deployments in the configuration file:

const config = defileConfig({
  deployments: {
    Constant1: { args: [/* args... */] },
    Constant2: { update: 'proxy', args: async env => [/* args... */] },
    Constant3: { update: 'uups', args: () => [/* ...args */] }
  }
})

Next, deploy to the desired chain:

$ pnpm harsta deploy --network [your network]

After deployment, the config/addresses.ts file will be automatically updated and recompiled.

Update

If your contract files are updated, rerun the script:

  • contracts/Constant1.sol has been modified
  • contracts/Constant2.sol has been modified

Rerunning the deploy script will redeploy the Constant1|Constant2 contracts.

If your contract is an upgradable contract, create a new file:

  • contracts/Constant1.sol original contract
  • contracts/Constant1V1.sol upgraded contract

Rerunning deploy will upgrade the contract.

Wagmi

harsta naturally supports wagmi. By adding a few lines of code, you can use the compiled information and contracts of all chains:

config.ts:

import { createConfig, http } from '@wagmi/core'
import { createClient } from 'viem'
import { chains } from '@harsta/client'

export const config = createConfig({
  chains: [chains.ethereum, chains.sepolia],
  client({ chain }) {
    return createClient({ chain, transport: http() })
  },
})

App.tsx:

import { WagmiProvider, useClient, useConnectorClient } from 'wagmi'
import { SubscribeWagmiConfig } from '@harsta/client/wagmi'
import { config } from './config'

function App() {
  return (
    <WagmiProvider config={config}>
      <SubscribeWagmiConfig
        useConnectorClient={useConnectorClient}
        useClient={useClient}
      />
      {/* your page content */}
    </WagmiProvider>
  )
}

Any page:

import { addresses, contracts } from '@harsta/client'
import { useAccount } from 'wagmi'

function Page() {
  const { isConnected } = useAccount()
  async function withdraw() {
    const lock = contracts.Lock.resolve('singer')
    const transaction = await lock.withdraw()
  }
  return (
    <>
      {isConnected && (
        <button onClick={withdraw}>
          Withdraw
        </button>
      )}
    </>
  )
}
export default Page

By modifying .env to change the default chain and configuration used by the contract:

NEXT_PUBLIC_DEFAULT_CHAIN = 'sepolia'
# OR
DEFAULT_CHAIN = 'sepolia'

Tests

You can ensure that deployment scripts are executed in tests by calling await deployments.fixture(['MyContract']). This is optimized so that if multiple tests use the same contract, the deployment will be done once, and each test will start in exactly the same state.

import { deployments, ethers } from 'hardhat'
import { expect } from 'chai'

describe('storage contract', () => {
  beforeEach(async () => {
    await deployments.fixture(['Storage'])
  })
  it('test', async () => {
    const Storage = await deployments.get('Storage')
    const storage = await ethers.getContractAt('Storage', Storage.address)
    // ...
  })
})

About

harsta is a contract development tool based on Hardhat, designed to streamline the development, testing, and referencing of contracts, addresses, ABIs, and contract instances.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published