DIMO continues to drive forward its mission to create the world’s first decentralized, open, connected vehicle network. If you follow the DIMO ecosystem, you’ve likely heard Yev, DIMO’s co-founder and CTO, talk about the vast amounts of data created by your vehicles, yet owned and controlled by vehicle brands, leaving owners out of the value exchange. DIMO is working to flip that script, allowing drivers to collect and access data about their vehicle, choose what data they’d like to share and with whom, and receive rewards for participation in the network. But DIMO’s open vehicle network unlocks another important value — to enable a developer ecosystem to build on top of this data, accelerating innovation and creating new use cases not possible in a closed economy.
Public & verifiable data
Gating data within the silos of car manufacturers stifles innovation and limits which players can contribute to the future of connected vehicles. This could lead to further privacy challenges if the same large tech companies that own most of our data, are the ones dictating how our vehicle data is being used and monetized. DIMO is combatting this with their open network, putting users in control of how data is used, and enabling developers to create value and get rewarded accordingly. A critical component to this success is ensuring they are using and creating reusable infrastructure that supports collaboration—this is where Tableland comes in.
Tableland provides a database solution that meets all the criteria DIMO was looking for: enable collaboration and composability over the data, allow for some level of ACL (to minimize spam entries), and ensure others can replicate the model (to encourage further creation of open information). Further, with the recent launch of Tableland Studio, it’s easier than ever for developers to find DIMO’s tables, build on top of the data, or recreate a similar structure for a different set of data.
Check out DIMO’s data in the Studio: https://studio.tableland.xyz/partners/dimo
This public good dataset is now deployed on mainnet and available for anyone to use. It is an important step toward making it easier for new projects and developers to build within the DIMO ecosystem. Read on to dive deeper into this collaboration and how it works.
The Public Dataset
DIMO and Tableland collaborated to create a decentralized, public dataset containing DIMO vehicle definitions. One of the primary benefits of open data is that it enables anyone to build on top of and use that information. The onchain-related components consist of:
A primary registry contract is the main entry point for adding or removing related module contracts.
A series of “node” contracts that include:
Manufacturer: Stores information about a vehicle’s or aftermarket device’s manufacturer.
Vehicle: Mints an NFT representation of the owner’s vehicle, including the manufacturer, owner, make, model, and year.
AftermarketDevice: Mints an NFT representation of an aftermarket IoT device, including the EVM associated address for the device, serial number, and hardware/IMEI information.
SyntheticDevice: Mints an NFT representation of a “synthetic device,” which is then mapped to a vehicle ID.
Integration: Mints an NFT representation that uniquely identifies an integration node.
A DIMO user pairs their EVM wallet with devices and vehicles, and then devices and vehicles are also mapped to one another. Specifically, the
AftermarketDeviceId
andVehicleId
are stored in the “device definitions” table in Tableland.Check out the implementation by the DIMO team here: https://github.com/DIMO-Network/dimo-identity
And the initial reference demo code by the Tableland team is below, which also has a unique setup with a dynamic merkle tree that represents the offchain table state fully onchain: https://github.com/tablelandnetwork/dimo-vehicle-defs
Why did we do it?
Tableland and DIMO set out to manage the device registry identities to help maintain and update the global list of valid vehicles fully transparently. While much of this data is public, DIMO spent considerable time and resources to source and format the data in a usable way and wants to simplify this for future use cases. For example, the onchain-driven and open dataset lets anyone query the vehicle registry and help compose data as part of the vehicle NFT metadata:
This also allows DIMO to seamlessly create new entries in the vehicle registry through onchain actions—without any additional database infrastructure setup—and it lets their end users see exactly what data about their vehicle is made available on the network. For an introduction to how we’re helping DePINs like DIMO, read our initial partnership blog post: here.
How does it work?
Let’s review the DIMO implementation noted above. There are two main contracts:
DeviceDefinitionTable: Manages table creation and mutations for vehicle definitions so that DIMO can add new manufacturer vehicle definitions to the network
Note: This manages vehicle information and should not be confused with the Device nor Vehicle contracts mentioned above.
DeviceDefinitionController: Additional access controller that ensures only the core smart contract can forward SQL write statements to the manufacturer/user-owned table.
Namely, table writes are gated by NFT ownership, but they still must pass through the smart contract so that valid and "authorized" SQL is written.
Plus, the DeviceDefinitionTableStorage contract provides a storage interface for a mapping that holds manufacturerId
and its tableId
.
Table creation
Every manufacturer minted by the Manufacturer
contract gets its own table. The following method is where a table is created, and information about the table’s creator/owner, manufacturer, and table ID is emitted in an event—called by an authorized manufacturer:
function createDeviceDefinitionTable(
address tableOwner,
uint256 manufacturerId
) {
// Assertions & SQL...
emit DeviceDefinitionTableCreated(tableOwner, manufacturerId, tableId);
};
The device definitions table has the following schema. The table is “nameless” but is created with a unique format like _{chainId}_{tableId}
.
CREATE TABLE _ (
id TEXT PRIMARY KEY,
model TEXT NOT NULL,
year INTEGER NOT NULL,
metadata TEXT,
ksuid TEXT,
devicetype TEXT,
imageuri TEXT,
UNIQUE(model, year)
)
When an authorized party calls createDeviceDefinitionTable
, they must make sure a Manufacturer
NFT is minted first and then pass the manufacturer ID to the method. A mapping in the contract tracks which manufacturer owns which table. The Solidity implementation of the table described above is the following, and you can see that address(this)
is used to mint the table to the actual contract:
string memory statement = string(
abi.encodePacked(
"CREATE TABLE _",
Strings.toString(block.chainid),
"(id TEXT PRIMARY KEY, model TEXT NOT NULL, year INTEGER NOT NULL, metadata TEXT, ksuid TEXT, devicetype TEXT, imageuri TEXT, UNIQUE(model,year))"
)
);
uint256 tableId = tablelandTables.create(address(this), statement);
Right after minting the table, the contract transfers ownership to the manufacturer/address that called the method. Before doing so, it sets the table’s controller to a Tableland controller contract (described below). This ensures that while the new owner does have full control of the table, the data they write to is must be written by the DIMO smart contract. In other words, all table writes are gated by the smart contract’s logic and available methods.
tablelandTables.setController(address(this), tableId, address(this));
INFT(address(tablelandTables)).safeTransferFrom(
address(this),
tableOwner,
tableId
);
From an access control perspective, the contract methods handle the assertions before an SQL is executed. The snippet below shows how this works for the table creation process by checking:
The passed
manufacturerId
(theManufacturer
contract’s NFT) is valid.The method’s caller has an admin role and is the owner of the token at
manufacturerId
.There isn’t an existing table created for the
manufacturerId
.
if (!manufacturerIdProxy.exists(manufacturerId)) {
revert InvalidManufacturerId(manufacturerId);
}
if (
!_hasRole(ADMIN_ROLE, msg.sender) &&
msg.sender != manufacturerIdProxy.ownerOf(manufacturerId)
) revert Unauthorized(msg.sender);
DeviceDefinitionTableStorage.Storage
storage dds = DeviceDefinitionTableStorage.getStorage();
TablelandTables tablelandTables = TablelandDeployments.get();
if (dds.tables[manufacturerId] != 0) {
revert TableAlreadyExists(manufacturerId);
}
And there is also a table “controller” definition that ensures this smart contract is the only one that can write to the table:
function getPolicy(
address caller,
uint256
) external payable returns (TablelandPolicy memory policy) {
if (caller == address(this)) {
policy = TablelandPolicy({
allowInsert: true,
allowUpdate: true,
allowDelete: true,
whereClause: "",
withCheck: "",
updatableColumns: new string[](0)
});
}
}
To reiterate—the manufacturer is the only one that can call the smart contract’s table mutation methods, and the smart contract is, technically, the one forwarding the SQL statements and doing the actual onchain calls to the Tableland registry.
Insert vehicle definitions
Once a table exists, the manufacturer can insert new definitions into the table. The insertDeviceDefinition
follows similar assertions as described above before inserting data, also using the INFTMultiPrivilege contract to manage permissions:
function insertDeviceDefinition(
uint256 manufacturerId,
DeviceDefinitionInput calldata data
) {
// Assertions & SQL...
emit DeviceDefinitionInserted(tableId, data.id, data.model, data.year);
}
The Solidity implementation is the following, where data
includes:
id
: The alphanumeric ID of the Device Definition.model
: The model of the Device Definition.year
: The year of the Device Definition.metadata
: The metadata stringfied object of the Device Definition.
tablelandTables.mutate(
address(this),
tableId,
SQLHelpers.toInsert(
"",
tableId,
"id,model,year,metadata,ksuid,devicetype,imageuri",
string.concat(
string(abi.encodePacked("'", data.id, "'")),
",",
string(abi.encodePacked("'", data.model, "'")),
",",
Strings.toString(data.year),
",",
string(abi.encodePacked("'", data.metadata, "'")),
",",
string(abi.encodePacked("'", data.ksuid, "'")),
",",
string(abi.encodePacked("'", data.devicetype, "'")),
",",
string(abi.encodePacked("'", data.imageuri, "'"))
)
)
);
Which, when seen by a Tableland validator node, gets emitted and materialized as the following SQL statement:
INSERT INTO
"_{chainId}_{tableId}" (id, model, year, metadata, ksuid, devicetype, imageuri)
VALUES
(
'{data.id}',
'{data.model}',
{data.year},
'{data.metadata}'
'{data.ksuid}',
'{data.devicetype}',
'{data.imageuri}'
)
Note that the {...}
values in the example statement above would be actual string-encoded values that were passed as part of the data
method parameter.
There are a few other available methods for retrieving a table’s name or ID as well as a batch insert variation.
How can someone use it?
All of this makes it possible to openly query the vehicle definitions by using Tableland clients like the SDK or REST API. Thus, a developer could build on top of the dataset or query information related to their DIMO vehicle.
The DIMO team made it easy for anyone to test out this setup, too, by providing scripts and instructions for deploying the contracts on Local Tableland. You can start by cloning their repo and checking out the device-defintions
branch:
git clone https://github.com/DIMO-Network/dimo-identity
cd dimo-identity
git checkout device-definitions
Then, check out the /scripts/tableland/instructions.md
file (here) to see the full guide. After installing npm packages, you can run the following script and tasks against a Local Tableland network:
npx hardhat run scripts/tableland/deployLocalTableland.ts --network localhost
npx hardhat --network localhost mint-manufacturer manufacturerNameExample
npx hardhat --network localhost create-dd-table <your_manufacturer_id>
This will log the manufacturer token and table information:
Minting manufacturer manufacturerNameExample for 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266...
Manufacturer manufacturerNameExample minted with ID: 142
Creating Device Definition table for manufacturer ID 142...
Device Definition table created
Table ID: 2
Table Name: _31337_2
Table Owner: 0xffffffffffffffffffffffffffffffffffffffff
Lastly, you can call the insertDeviceDefinition
or even use something like the Tableland SDK to insert data and expand the dataset. Recall that the access controls limit table mutations such that only the smart contract has the right to mutate data, and only the table’s owner (i.e., the manufacturer) has the right to call the method.
Learn more
Textile
We’ll continue to put out additional walkthroughs of our partner and general use case implementations in long-form posts. But, if you’d like to stay up to date with what we’re building on a week-by-week basis, follow us here on Paragraph.xyz (it's what our blog site uses), or, hop into our Discord for the latest updates: here.
DIMO
Visit the DIMO Website to learn more about how to get involved as a user or developer in their network, and follow them on X for the latest updates.