Hardhat
Use Tableland smart contracts with Hardhat.
Hardhat is an EVM development platform that comes packed with plenty of tools to get up and running. Using npx
, you can quickly spin up a project with starter contracts and scripts to help deploy your application. This will quickly walk through Hardhat-specific usage, but do review the more detailed quickstart or general smart contract documentation for creating and mutating tables.
1. Installation & setup
Create a folder and then cd
into it to set up a Hardhat app. Follow the starter steps by choosing a project type (JavaScript, TypeScript, or empty) and other configurations.
npx hardhat
Then, install @tableland/evm
as a dependency—optionally, @openzeppelin/contracts
's Strings
are also useful.
- npm
- Yarn
- pnpm
npm install --save @tableland/evm @openzeppelin/contracts
yarn add @tableland/evm @openzeppelin/contracts
pnpm add @tableland/evm @openzeppelin/contracts
Tableland also has a useful @tableland/hardhat
development dependency that can be used when developing locally. It extends the HardhatUserConfig
object with an optional localTableland
field of type Config
, which allows you to configure how Local Tableland (local Hardhat + Tableland node) will run.
- npm
- Yarn
- pnpm
npm install --save-dev @tableland/hardhat
yarn add --dev @tableland/hardhat
pnpm add --save-dev @tableland/hardhat
Under contracts
, find the Lock
contract (remove its code) and import TablelandDeployments
and SQLHelpers
from @tableland/evm
. A tableId
can be used to track the minted table.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "@tableland/evm/contracts/utils/TablelandDeployments.sol";
import "@tableland/evm/contracts/utils/SQLHelpers.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract Lock is ERC721Holder {
// The table token ID, assigned upon `TablelandTables` minting a table
uint256 public tableId;
// Table prefix for the table (custom value)
string private constant _TABLE_PREFIX = "my_hardhat_table";
}
2. Create & insert to a table
Create a table that is sent to the caller (msg.sender
)—note that if you want the contract to own the table, you'll have to implement contract ERC721 ownership.
contract Lock is ERC721Holder {
uint256 public tableId;
string private constant _TABLE_PREFIX = "my_hardhat_table";
// Add a constructor that creates and inserts data
constructor() {
tableId = TablelandDeployments.get().create(
address(this),
SQLHelpers.toCreateFromSchema(
"id integer primary key," // Notice the trailing comma
"val text",
_TABLE_PREFIX
));
TablelandDeployments.get().mutate(
address(this),
tableId,
SQLHelpers.toInsert(
_TABLE_PREFIX,
tableId,
"id,val",
string.concat(
Strings.toString(1), // Convert to a string
",",
SQLHelpers.quote("Bobby Tables") // Wrap strings in single quotes with the `quote` method
)
)
);
}
}
3. Adjust config & script
With @tableland/hardhat
, you should import the plugin in your hardhat.config.js
and adjust the object:
require("@nomicfoundation/hardhat-toolbox");
require("@tableland/hardhat");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.18",
localTableland: {
silent: false,
verbose: false,
},
};
Navigate to the scripts
directory and find the deploy.js
script. Some slight alterations are needed to reflect the changes above.
const hre = require("hardhat");
async function main() {
const Lock = await hre.ethers.getContractFactory("Lock");
const lock = await Lock.deploy();
await lock.deployed();
console.log(`Lock deployed to ${lock.address}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Optionally, adding the @tableland/sdk
can be useful for querying table data or retrieving other information, like the table's name.
- npm
- Yarn
- pnpm
npm install --save-dev @tableland/sdk
yarn add --dev @tableland/sdk
pnpm add --save-dev @tableland/sdk
Then, update the deploy script to log some of this information.
const hre = require("hardhat");
const { Database, Validator, helpers } = require("@tableland/sdk");
async function main() {
const Lock = await hre.ethers.getContractFactory("Lock");
const lock = await Lock.deploy();
await lock.deployed();
console.log(`Lock deployed to ${lock.address}`);
const [signer] = await hre.ethers.getSigners();
const chainId = await signer.getChainId();
const db = new Database({
signer,
baseUrl: helpers.getBaseUrl(chainId),
});
const validator = new Validator(db.config);
const name = await validator.getTableById(tableId);
const data = await db.prepare(`SELECT * from ${name}`).all();
console.log(`Data in table '${name}':`);
console.log(data);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
4. Start a node & deploy locally
Start a Hardhat and Local Tableland node:
npx hardhat node --network local-tableland
Now that nodes are running, you can deploy your contract. This will create and write tables using the Hardhat node, and then the Local Tableland node will materialize emitted SQL events and allow for read queries at the HTTPS gateway. Run the following in another terminal window:
npx hardhat run scripts/deploy.js --network localhost
It should print some deployment information:
Lock deployed to 0x98dd705fBD9B12B90b8C997afd0362EB7a9fbe37
Data in table 'my_hardhat_table_31337_2':
[
{
"id": 0,
"name": "Bobby Tables"
}
]
If, instead, you want to simply start up the nodes, deploy your contracts, and then shutdown the nodes (i.e., don't keep them running), you can choose to run the deploy script with local-tableland
as the network. This means that you should not run the npx hardhat node
command in a separate window since there will be a port clash.
npx hardhat run scripts/deploy.js --network local-tableland
5. Deploy to live networks
If you want to deploy to a testnet or mainnet chain, you'll need to adjust the --network
flag, which pulls information from the hardhat.config.js
file. For example, you could add Ethereum sepolia
as a network. To do this, you'll have to use a provider like Alchemy with some account variables.
const ALCHEMY_API_KEY = "API_KEY";
const SEPOLIA_PRIVATE_KEY = "SEPOLIA_PRIVATE_KEY";
module.exports = {
// ...
networks: {
sepolia: {
url: `https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_API_KEY}`,
accounts: [SEPOLIA_PRIVATE_KEY],
},
},
};
This will allow you to deploy to a live network:
npx hardhat run scripts/deploy.js --network sepolia