Tableland-based DIMO Vehicle Definitions demo
This is a demo of hosting the DIMO vehicle definitions table from the DIMO VehicleId
ERC721 contract using Tableland. The table is owned by and written to by the VehicleId
contract. VehicleId
tokens represent user vehicles and are associated with a vehicle definition (e.g. 2011 Toyota Tacoma). The crux of the problem is how to ensure that a given vehicle definition exists when a new VehicleId
is minted from the contract.
We show two potential solutions:
-
VehicleId
simply increments a counter whenever a new vehicle definition is added. When minting aVehicleId
, the user passes in an integer that represents the Tableland row ID (auto-incrementing primary key) of a vehicle definition. Since table rows cannot be deleted, if this integer is greater than the current counter, we know it’s invalid and the transaction is rejected. -
VehicleId2
leverages a (somewhat experimental) on-chain dynamic merkle tree to track a root hash that represents the entire off-chain vehicle definitions table. When minting aVehicleId
, the user passes in the Tableland row ID (auto-incrementing primary key) of a vehicle definition, a hash of its values (make, model, year, etc.), and a merkle inclusion proof that it actually exists in the off-chain table. The contract only needs to store a singlebytes32
representing the root hash. This approach is more heavy-handed, but ensures a tighter coupling between theVehicleId
and vehicle definition.Note that the proofs needs to be generated off-chain from the table state. Ideally, validators
go-tableland
should have an API endpoint that allows users and apps to fetch inclusion proofs for a given row by internally maintaining a merkle tree for the table. As a user, you wouldn't have to trust what the validator gives you because verification happens on-chain against a root hash that is dynamically updated when rows are added/updated (remember, in this case the table can only be written to by the contract).
We also show how the ERC721 token metadata could be handled dynamically from the vehicle definitions table. The tokenUri method returns a Tableland query that selects from the table using the internal VehicleId to vehicle definition mapping (see the query here). The result is that we get token metadata that can change based on its associated vehicle definition. Note, this is only implemented for VehicleId
.
Here's what the metadata for a VehicleId
might look like.
{
"name": "VehicleId #1",
"attributes": [
{
"trait_type": "device_type_id",
"value": "vehicle"
},
{
"trait_type": "make",
"value": "Ford"
},
{
"trait_type": "make_token_id",
"value": 41,
"display_type": "number"
},
{
"trait_type": "oem_platform_name",
"value": "FordPass"
},
{
"trait_type": "model",
"value": "F-250 Super Duty"
},
{
"trait_type": "year",
"value": 2018,
"display_type": "number"
},
{
"trait_type": "model_style",
"value": "King Ranch 4dr Crew Cab SB (6.2L 8cyl 6A)"
},
{
"trait_type": "model_sub_style",
"value": "King Ranch"
}
],
"metadata": {
"vehicle_info": {
"base_msrp": "44600",
"fuel_type": "Diesel",
"wheelbase": "164 WB",
"generation": "13",
"driven_wheels": "4x4",
"number_of_doors": "4",
"manufacturer_code": "W2B",
"fuel_tank_capacity_gal": "34"
}
}
}
There's a lot we could do with the metadata structure. We could JOIN
from other tables, remove/add attributes, add an image/animation, etc. Note, because most common metadata format doesn't support nested object traits in attributes
, we just add the metadata
column from the vehicle definitions table as a top level property.
You can build the Typescript client locally:
npm install
npm run build
Run the test suite:
npm test
Deployments are handled on a per-network basis:
npx hardhat run scripts/deploy.ts --network polygon
You can grab the assets you need by compiling and then using some jq
magic:
cat artifacts/contracts/VehicleId.sol/VehicleId.json | jq '.abi' > abi.json
cat artifacts/contracts/VehicleId.sol/VehicleId.json | jq -r '.bytecode' > bytecode.bin
You can use the above abi.json
to build the Go client:
mkdir gobuild
abigen --abi ./abi.json --bin ./bytecode.bin --pkg contracts --out gobuild/VehicleId.go
To perform Etherscan verification, you first need to deploy a contract to an Ethereum network that's supported by Etherscan, such as Polygon:
npx hardhat run scripts/deploy.ts --network polygon
Then, add a POLYSCAN_API_KEY
to your .env
file and run the verify script:
npx hardhat run scripts/verify.ts --network polygon
For faster runs of your tests and scripts, consider skipping ts-node's type checking by setting the environment variable TS_NODE_TRANSPILE_ONLY
to 1
in hardhat's environment. For more details see the documentation.