https://github.com/hairyf/harsta
https://github.com/hairyf/harsta
builder contract exporter hardhat hardhat-deploy viem wagmi
Last synced: 6 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/hairyf/harsta
- Owner: hairyf
- License: mit
- Created: 2025-01-24T09:18:31.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2025-04-11T14:11:49.000Z (6 months ago)
- Last Synced: 2025-04-22T21:02:52.239Z (6 months ago)
- Language: TypeScript
- Size: 3.38 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
## 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.

- [Address visualization and third-party ABI](#directory)
- [Common Harsta Config for Browser and Node.js](#config)
- [Automatic deployment based on config](#deployments)
- [Seamless Wagmi support](#wagmi)
- [Rapid deploy tests](#tests)## Installation
You can quickly create a project like Hardhat using the command `pnpm harsta create`, or you can install it separately in any project:
```sh
$ pnpm harsta create# OR
$ pnpm install harsta @harsta/client
# OR
$ pnpm install 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 | config/addresses.json`
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.
```ts
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:
```sh
harsta compile Compile and output the directory
harsta test Run integration tests
harsta deploy Deploy and save deployments
harsta update Update deployed upgradable contracts
harsta node Starts a JSON-RPC server on top of Local Network
```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:
```ts
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:
```ts
import { contracts, updateRunner } from '@harsta/client'
// Update the default runner (signer) 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.
```ts
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:
```ts
const config = defileConfig({
// ...
deployments: {
Contract1: { args: [/* args... */] },
Contract2: { kind: 'transparent', args: async env => [/* args... */] },
Contract3: { kind: 'uups', args: () => [/* ...args */] },
Contract4: { target: 'Contract1', args: [/* args... */] },
Contract5: { target: 'Contract1', chains: [/* chainIds */] },
}
})
```Additionally, we can tell `harsta` to verify our contract on Etherscan, Sourcify or Blockscout, if the network is supported, by passing `--verify`.
Next, deploy to the desired chain:
```sh
$ pnpm harsta deploy --network [your network] --verify
```If successful, output the following info:
```sh
TARGET > :.sol
NETWORK >
Hash >
From >
Args >
---------------------------------------------------------
Address >
```If it is an upgradable contract, then it is:
```sh
TARGET > :.sol
NETWORK >
kIND >
Hash > (implement)
From >
---------------------------------------------------------
Hash > ()
From >
Args >
---------------------------------------------------------
Implement >
Proxy >
```After deployment, the `config/addresses.ts` and `config/deployments` file will be automatically updated.
## Verify
It is recommended to use the `--verify` flag with `harsta deploy` to automatically verify the contract on explorer after a deployment.
If you are verifying an already deployed contract, read on.
Before this, you need to set the `verify` field for the corresponding network:
```ts
import { defineConfig } from 'harsta'const config = defineConfig({
// ...
networks: {
ethereum: {
// ...
verify: { uri: '...', key: '...' }
}
},
})
```And you can verify a contract on Etherscan, Sourcify, oklink or Blockscout with the `harsta verify [target]` command.
## Update
If your contract files are updated:
- `contracts/Constant1.sol` has been modified
- `contracts/Constant2.sol` has been modifiedrun `deploy --contracts Constant1, Constant2` script will ask and redeploy `Constant1 | Constant2` contracts.
If your contract is an upgradable contract, create a new file:
- `contracts/Constant1.sol` original contract
- `contracts/Constant1V1.sol` upgraded contractrun `update Constant1 --target Constant1V1` will upgrade the contract.
```sh
TARGET > :.sol
NETWORK >
kIND >
Hash > (implement)
From >
---------------------------------------------------------
Hash > (upgradeTo)
From >
---------------------------------------------------------
Implement >
←
Proxy >
```## 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:
```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:
```tsx
import { WagmiProvider, useClient, useConnectorClient } from 'wagmi'
import { SubscribeWagmiConfig } from '@harsta/client/wagmi'
import { config } from './config'function App() {
return (
{/* your page content */}
)
}
```Any page:
```tsx
import { addresses, contracts } from '@harsta/client'
import { useAccount } from 'wagmi'function Page() {
const { isConnected } = useAccount()
async function withdraw() {
const lock = contracts.Lock.resolve('signer')
const transaction = await lock.withdraw()
}
return (
<>
{isConnected && (
Withdraw
)}
>
)
}
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.
```ts
import { contracts } from 'harsta/runtime'
import { fixture, initial } from 'harsta/tests'
import { expect } from 'chai'describe('storage contract', () => {
beforeEach(async () => {
await initial()
await fixture(['Storage'])
})
it('test', async () => {
const storage = contracts.Storage.resolve()
// ...
})
})
```