JavaScript SDK

JavaScript SDK

API Reference for the integrated JavaScript / TypeScript SDK.

The @tableland/sdk provides a zero-config TypeScript/JavaScript SDK that should feel comfortable to developers already familiar with the ethers Javascript library. It provides a small but powerful API surface that integrates nicely with existing EVM development best practices, such as hardhat. Namely, it includes functions for connecting to remote clients, creating and mutating tables, querying existing tables, and a few helpers, including listing all user tables. These top-level exports are available as individual functions.

Simply import the library, connect to the Tableland network, and you are ready to start creating and updating tables!

ℹ️
Interested in Tableland supporting additional chains and ecosystems or languages? Create an issue on GitHub and let us know! Create an Issue

Setup

⚠️
The Tableland SDK uses the modern fetch API. When working in Node, it is necessary to use a version of Node (v18+) that supports fetch, or provide global access to node-fetch to use the SDK (e.g., for Node v16 or earlier). See Example

Install

You can install the SDK via npm:

npm install --save @tableland/sdk

Or yarn:

yarn add @tableland/sdk

Be sure to review the prerequisites section for potential setup steps, including having testnet currency to leverage any testnets (use faucets for our supported EVM-compatible blockchains).

⚠️
Tableland is still in open beta and will be launching the production network in 2023. But, smart contracts and apps deployed on testnet and mainnet chains can and should use the Tableland during the open beta period. Developers should still proceed with caution due to the nature of opne beta changes and ensure contracts that use Tableland are future-proof.

Note that a couple of methods — setController and write — allow for an rpcRelay to be set. This a boolean identifier for whether or not a smart contract call should be relayed by the Tableland validator or not.

Fees

While using the Tableland testnet, developers can leverage the rpcRelay flag, which defaults to true in testnet environments. This means that on-chain transactions like table writes are covered for, such that developers do not need to pay for transaction fees. Mainnet transactions are not relayed, so developers will have to pay for these transactions.

Regardless, it’s a good idea to be sure to have enough currency in your wallet before using Tableland!

Overview

Using Tableland follows a familiar flow to those who have worked with typical relational databases. Namely, the following steps are going to be used in nearly every application:

  1. connect ⇒ establish a connection with a Tableland and the desired chain
  2. create ⇒ pass a schema to create a table that’s uniquely identifiable across any chain
  3. write ⇒ insert or update values in the table (INSERT, UPDATE, DELETE, GRANT, REVOKE)
  4. read ⇒ query tables for their data (SELECT, FROM, WHERE)

There are a number of other helper functions with meaningful utility, but the most commons ones are those listed above. Knowing this, the following provides some starter code for how to leverage Tableland, starting with client side JavaScript.

Client-Side Apps

First, import the connect method, which is the primary entry point to connect to the base chain and the Tableland network. Recall that Tableland is dependent on on-chain SQL instructions to then materialize in an off-chain, decentralized network of SQL; the SDK connects to both of these.

// Import `connect` from the Tableland library
import { connect } from "@tableland/sdk";

// Connect to the Tableland testnet (defaults to Polygon Mumbai testnet chain)
// @return {Connection} Interface to access the Tableland network and, optionally, a target `chain`
const tableland = await connect({ network: "testnet", chain: "polygon-mumbai" });
// For client-side apps, call `siwe` to prompt a browser wallet sign-in flow
await tableland.siwe();

// Create a new table with a supplied SQL schema and optional `prefix`
// @return {Connection} Connection object, including the table's `name`
const { name } = await tableland.create(
  `id integer primary key, name text`, // Table schema definition
  {
    prefix: `my_sdk_table` // Optional `prefix` used to define a human-readable string
  }
);

// The table's `name` is in the format `{prefix}_{chainId}_{tableId}`
console.log(name); // e.g., my_sdk_table_80001_311
// Without the supplied `prefix`, `name` would be be `_80001_311`

// Insert a row into the table
// @return {WriteQueryResult} On-chain transaction hash of the write query
const writeRes = await tableland.write(`INSERT INTO ${name} (id, name) VALUES (0, 'Bobby Tables');`);

// Perform a read query, requesting all rows from the table
const readRes = await tableland.read(`SELECT * FROM ${name};`);
// Note: a table *must first exist* in Tableland before performing `read`.
// Similarly, a `write` must first be included in a block before it's accessible in `read`.
// See the utility function `waitConfirm` (not an export) to validate when Tableland performed the instructions.
// Function here: https://github.com/tablelandnetwork/js-tableland/blob/46c3fb8f47f6c13721b3e3cff5a2cb01ddbd04d7/src/lib/util.ts#L122

Node.js

Import

First, install the ethers library and import Wallet and providers. Set your private key as the "PRIVATE_KEY_STRING", which, ideally, should only be accessed using a .env file and dotenv. Then, establish the wallet as well as the provider. Lastly, connect the wallet to the provider, and use the corresponding signer to connect to the base chain and Tableland network.

// Import `connect` from Tableland plus wallet requirements from `ethers`
import { Wallet, providers } from "ethers";
import { connect } from "@tableland/sdk";

// Since Metamask is not accessible via browser injection,
// it is required to supply a private key.
// Do not expose this key directly but load from a `.env` file
const privateKey = "PRIVATE_KEY_STRING";
const wallet = new Wallet(privateKey);

// An RPC provider must be provided to establish a connection to the chain
const provider = new providers.AlchemyProvider("maticmum", "ALCHEMY_API_KEY");
// By default, `connect` uses the Tableland testnet validator;
// it will sign a message using the associated wallet
const signer = wallet.connect(provider);
const tableland = await connect({ signer, network: "testnet", chain: "polygon-mumbai" });

// Create a new table with a supplied SQL schema and optional `prefix`
// (Same logic as the client code in the section above)

require

Using require, the setup is similar as import but slightly differs in the the way connect is used:

// Require `connect` from Tableland plus wallet requirements from `ethers`
const ethers = require("ethers");
const tbl = require("@tableland/sdk");

// Since Metamask is not accessible via browser injection,
// it is required to supply a private key.
// Do not expose this key directly but load from a `.env` file
const privateKey = "PRIVATE_KEY_STRING";
const wallet = new ethers.Wallet(privateKey);

// An RPC provider must be provided to establish a connection to the chain
const provider = new ethers.providers.AlchemyProvider("maticmum", "ALCHEMY_API_KEY");
// By default, `connect` uses the Tableland testnet validator;
// it will sign a message using the associated wallet
const signer = wallet.connect(provider);
const tableland = await tbl.connect({ signer, network: "testnet", chain: "polygon-mumbai" });

// Create a new table with a supplied SQL schema and optional `prefix`
// (Same logic as the client code in the section above)

Connecting to Tableland

The connect function is the first step in connecting to and using Tableland. The SDK has exposed a Sign In With Ethereum (SIWE) method (siwe) so that developers don’t need to set up a wallet sign in workflow. Simply call connect with the desired network and (optionally) chain. Then, call siwe for a client side prompt to “sign” a message.

Put simply, it’s a way to prove that an account approved a message. This signature and message will be encoded into a token that is used to verify ownership of the provided signer’s address and to avoid the user having to sign subsequent Tableland transactions/method calls.

connect

The method takes an optional object that is materialized in the returned Connection object, which contains all of information and methods needed to interface with both Tableland and the target chain’s smart contracts.

Parameters

  • <Object> (optional) ⇒ Object (ConnectOptions) of connection parameters.
    • token (optional) ⇒ Override any required signature by using a pre-signed SIWE token.
    • signer (optional) ⇒ Signer interface for signing tokens and transactions.
    • host (optional) ⇒ Remote gateway host for RPC API calls.
    • network (optional) ⇒ String enum indicating Tableland network. Defaults to "testnet".
    • chain (optional) ⇒ String enum indicating the target EVM chain. Defaults to polygon-mumbai or signer.provider.getNetwork, if available.
    • chainId (optional) ⇒ Corresponding id for the chain.
    • contract (optional) ⇒ Contract address to use for Tableland Tables registry. If provided, overrides defaults derived from network + chain combination.
    • rpcRelay (optional) ⇒ Boolean that indicates if tableland writes should be relayed via the Tableland Validator node. This can only be true for testnets.

Returns

Connection
interface Connection {
  token?: Token;
  signer?: Signer;
  options: {
    host: string;
    network: NetworkName;
    chain?: ChainName;
    contract: string;
    chainId: number;
    rpcRelay: boolean;
  };
  list: () => Promise<TableMetadata[]>;
  create: (
    /** The schema that defines the columns and constraints of the table,
     *  e.g.
     `id int NOT NULL,
      name char(50) NOT NULL,
      favorite_food char(50),
      PRIMARY KEY (id)`
     */
    schema: string,
    /**
     *  an optional options argument to specify conditions of create.
     **/
    options?: CreateOptions
  ) => Promise<CreateTableReceipt>;
  read: (query: string) => Promise<ReadQueryResult>;
  write: (query: string, options?: WriteOptions) => Promise<WriteQueryResult>;
  hash: (schema: string, options?: HashOptions) => Promise<StructureHashResult>;
  receipt: (txnHash: string) => Promise<ReceiptResult | undefined>;
  setController: (
    controller: string,
    name: string,
    options?: SetControllerOptions
  ) => Promise<WriteQueryResult>;
  getController: (tableName: string) => Promise<string>;
  lockController: (tableName: string) => Promise<WriteQueryResult>;
  siwe: () => Promise<Token>;
  validate: (query: string) => Promise<ValidateWriteResult>;
  waitConfirm: (
    txnHash: string,
    options?: ConfirmOptions
  ) => Promise<ReceiptResult>;
  schema: (tableName: string) => Promise<SchemaQueryResult>;
  structure: (tableName: string) => Promise<StructureQueryResult[]>;
}

Example

A common pattern is to call connect and save the connection value as tableland:

// Import `connect` from the Tableland library
import { connect } from "@tableland/sdk";

// By default, `connect` uses the Tableland testnet and "polygon-mumbai" testnet chain.
// This will sign a message using the associated account `signer`.
const tableland = await connect({ network: "testnet", chain: "polygon-mumbai" });
Node.js (import)
// Import `connect` from Tableland plus wallet requirement from `ethers`
import { Wallet } from "ethers";
import { connect } from "@tableland/sdk";

// Since Metamask is not accessible via browser injection,
// it is required to supply a private key.
// Do not expose this key directly but load from a `.env` file
const privateKey = "PRIVATE_KEY_STRING";
const signer = new Wallet(privateKey);

// By default, `connect` uses the Tableland testnet and "polygon-mumbai" testnet chain.
// This will sign a message using the associated account `signer`.
const tableland = await connect({ signer, network: "testnet", chain: "polygon-mumbai" });
Node.js (require)
// Require `connect` from Tableland plus wallet requirement from `ethers`
const ethers = require("ethers");
const tableland = require("@tableland/sdk");

// Since Metamask is not accessible via browser injection,
// it is required to supply a private key.
// Do not expose this key directly but load from a `.env` file
const privateKey = "PRIVATE_KEY_STRING";
const signer = new ethers.Wallet(privateKey);

// By default, `connect` uses the Tableland testnet and "polygon-mumbai" testnet chain.
// This will sign a message using the associated account `signer`.
const tbl = await tableland.connect({ signer, network: "testnet", chain: "polygon-mumbai" });

For client side apps, most connect workflows will be accompanied by the siwe method. Calling siwe will prompt the for the user’s wallet (e.g., MetaMask) to sign a message.

const tableland = await connect({ chain: "polygon-mumbai" });
// After establishing a connection, prompt a browser wallet sign-in process.
await tableland.siwe();

Note that if the connection has an rpcRelay set to false, a client side app could potentially get away without ever calling siwe.

For connecting to a specific chain, pass the chain or chainId of the respective chain in connect, which are optional. Details on these parameters can be found at the bottom of this page under Development Reference. See Reference

Relays on Testnets, Not Mainnets

The connect method has an option for rpcRelay. This is a boolean identifier for whether or not a smart contract call should be relayed by the Tableland network. It defaults to true for testnet chains, and it cannot be true for mainnet chains. See the SUPPORTED_CHAINS section for the default values for each chain.

A relay is the process of using an intermediary to send a transaction. On testnet chains, relaying makes it very easy for developers to use Tableland since the default option is for a Tableland validator node to relay the transaction. Thus, the account / wallet sending the transaction does not need to pay for the testnet transaction. Note that various SDK methods, such as write, can have the rpcRelay set at the method-level, taking precedence of the option set in the top-level connect.

⚠️
Mainnet transactions are not free and cannot be relayed. Relays provide a free experience while testing and are a testnet only feature where Tableland validator nodes pay for the testnet transaction to be sent.

Creating Tables

When tables are created, the underlying interaction is a smart contract call. Thus, when creating a table, there will be a waiting period while a transaction from the smart contract call is waiting to be confirmed. This means that different chains will have different waiting periods for a transaction to finalize, so there exists “waiting” that implements polling with exponential backoff up to a timeout.

create

Similar to most relational database systems, Tableland requires the user to create tables for storing, querying, and relating data. The returned object (instance of CreateTableReceipt) allows developers to see the outcome of their create in terms of the on-chain smart contract interaction. The name and txnHash fields are particularly useful.

Parameters

  • schema ⇒ Column names, datatypes, and constraints that would go inside the parenthesis of a typical CREATE TABLE statement. For example: id int, name string, primary key (id).
  • options (optional) ⇒ Object (CreateOptions) of table creation options.
    • prefix (optional)⇒ Human readable string of character to help identify the table’s name.
    • skipConfirm (optional)⇒ Boolean value to bypass “waiting” for a transaction to be confirmed. Defaults to false, meaning, the return will happen upon tx confirmation; if true, returns immediately.
    • timeout (optional) ⇒ Timeout for waiting until a transaction has been materialized by the Tableland network (using waitConfirm under the hood). Defaults to 2 minutes (120000 ms).

Returns

  • <Object> ⇒ Object (CreateTableReceipt) containing details about the created table.
    • tableId (optional) ⇒ The minted ERC721 TABLE token's id from the smart contract.
    • name (optional) ⇒ Name of the table in the format {prefix}_{chainId}_{tableId}.
    • prefix (optional) ⇒ Optional prepended string to help with readability.
    • chainId ⇒ Target chain id (e.g., `80001` means Polygon Mumbai).
    • txnHash ⇒ Transaction hash created from calling the smart contract.
    • blockNumber ⇒ The block the txnHash was included in.

Example

// Create a new table with a supplied SQL schema and optional `prefix`
// @return {CreateTableReceipt} On-chain Table creation receipt with chain details
const { name } = await tableland.create(
  `id integer, name text, primary key (id)`, // Table schema definition
  {
    prefix: `my_sdk_table` // Optional `prefix` used to define a human-readable string
  }
);

// For context, the full reponse object looks like the following
// {
//    "tableId": "311",
//    "prefix": "my_sdk_table",
//    "chainId": 80001,
//    "txnHash": "0x29a096f15ce6e70ad3cc7c85552d33ad903c9902a6d80d26b344a6b125199163",
//    "blockNumber": 7135948,
//    "name": "my_sdk_table_80001_311"
// }

Table Names

All tables in Tableland are unique. Each table is created using the corresponding chain’s chainId and a unique table tableId that is generated by the Tableland smart contracts (where each table is technically an ERC721 TABLE token). The optional prefix allows developers to specify a human-readable table identifier, which is prepended to a string concatenated (using underscores) with the chain chainId and table id.

Putting these pieces together — the unique identifier for any table across any chain is in the format: {prefix}_{chainId}_{tableId}

// E.g., a table on Ethereum (chainId `1`) & minted as token id `2`
// with prefix `my_sdk_table`
console.log(name) // my_sdk_table_1_2

// Or, if `prefix` was not specified, it'd simply be: _2_1

The prefix must follow standard SQL table naming conventions — i.e., it cannot start with a number, and only ASCII characters, numbers, and underscores may be used. For more on the rules of SQL table names, see our documentation on naming tables. Note that Tableland is a subset of the SQLite SQL language specification; thus, most valid SQL constraints are supported, and the following data types are currently supported:

  • integer, intrealtextblobany

See also the full SQL Specification for more details on representing other common SQL data types using the above core types.

Schema Validation with hash

Every table created in Tableland will have an associated structure. It is generated based on the schema definitions such that any table with the same schema with have the same structure (a hash). Thus, the hash method is useful for application builders, particularly, for validating CREATE TABLE SQL statements.

As a best practice, use the hash method to prior to creating a table. It’s important to note that the table’s name passed here is a little different than the previous examples. Here, use the format {prefix}_{chainId}. This validates the following:

  1. The table naming convention is SQL compliant (e.g., does not start with numbers)
  2. The appended chainId is supported (e.g., 80001 represents polygon-mumbai)

If either the table naming convention or the chainId are not supported, hash will return an error:

// Check if your statement is valid
// Note the parameter must have the correct `chainId` appended to the table `prefix`
try {
  // Table names cannot start with a number...
  const hashRes = await tableland.hash('id int primary key, val text', '123my_sdk_table')
} catch (err) {
  console.log(err.message) // invalid sql near 123
}

// A well-formatted table `hash` response
const hashRes = await tableland.hash('id int primary key, val text', 'my_sdk_table')
console.log(hashRes)
// {
//   structureHash: '6cda2617538ac6f725ece008947d07a8088433f982c6690d2811d0d53f74c398'
// }

A quick note — recall that Tableland has made is easy to add tables to the network using create, which only takes the schema and optional prefix. The hash function includes the typical CREATE TABLE statement, but this format isn’t needed due to abstraction mentioned for in create.

Listing Tables by Address

list

Once tables have been created for a given address, they can be listed via the list function. This function takes no arguments and returns a list of all TableMetadata objects owned at the address for the connected chain.

Parameters

N/A

Returns

  • <Array> ⇒ Array of objects describing tables owned by the signer’s address (e.g., address coming from SIWE process or passed as an env var).
    • <Object> ⇒ Object containing information about a table.
      • controller ⇒ Owner of the table.
      • name ⇒ Name of the table.
      • structure ⇒ Generated structure hash that describes the table’s schema.

Example

//	Lists all tables corresponding to the passed address
//	@return {TableMetadata} Metadata about the table from the Tableland validator network
const tables = await tableland.list();
console.log(tables)
// [
//   {
//     controller: '0x4D5286d81317E284Cd377cB98b478552Bbe641ae',
//     name: 'my_sdk_table_80001_311',
//     structure: '466dc130f3b02cf995fb66f6a0bdbadc49d2a527c26ac328daddc3f7b8ef779c'
//   }
// [

An application can use the list function in a number of way, one of which is to discover a user's tables at a certain address to determine if they are relevant for the given application.

🚧
Since all table reads are always openly accessible, it is not advised to store sensitive or private information on the Tableland network at this time.

Note that combining hash with the list method can be helpful when determining if an address has an owned table with a matching structure:

// Check if a user already has `mytable` on Polygon Mumbai (which has chainId `80001`)
const hashRes = await tableland.hash('name text, id int, primary key (id)', 'my_sdk_table')
const appTableStructure = hashRes.structureHash;

const tables = await tableland.list(); // returns an Object with the Tables the connected address owns
const myMatchingTables = Object.values(tables).filter((table) => table.structure === appTableStructure) // filters `tables` for matching `structure`s

// Now the application might want to prompt the user to create `mytable` if it doesn't exist,
// or read from it if it does exist.

Querying Tables

write

Use vanilla SQL statements to insert new rows, update existing rows, and even delete old rows. Namely, the write function can be used to mutate table rows — it takes a single SQL statement and returns an object with the transaction hash of the write statement (instance of WriteQueryResult).

Parameters

  • statement ⇒ SQL mutating query statement (e.g., INSERT, UPDATE, etc.).
  • options (optional) ⇒ Object (WriteOptions) of write query options.
    • skipConfirm (optional) ⇒ Boolean to skip “waiting” for an on-chain transaction to confirm. Defaults to false.
    • rpcRelay (optional) ⇒ Boolean to use Tableland nodes to relay on-chain transactions. Defaults to true for testnets and is not possible on mainnets (must be false).

Returns

  • <Object> ⇒ Object (WriteQueryResult) containing on-chain transaction hash generated from the write query.
    • hash ⇒ Transaction hash corresponding to the write query.

Example

// Runs a mutating query statement at the specified table
// @return {WriteQueryResult} On-chain transaction hash of the write query
const insertRes = await tableland.write(
  `INSERT INTO ${name} (id, name) VALUES (0, 'Bobby Tables');`
);
console.log(insertRes)
// {
//   "hash": "0xbe8c14686af7c2c71c5a996923816d20e31d59a1a976962b443f93a96a031f2d"
// }

// If desired, the row can later be removed
const removeRes = await tableland.write(`DELETE FROM ${name} WHERE id = 0;`);
console.log(removeRes)
// {
//  "hash": "0xf1aef7e64e941de64551978e72da24072bc5497aebe257b2383d697d7a7a9b87"
// }
⚠️
Currently, table mutations are restricted to the table creator address while using the SDK.

As noted in the SQL spec, the following are valid SQL statements to use:

  • INSERT ⇒ insert values into a table (that aligns to the schema)
  • UPDATE ⇒ update existing values in a table
  • DELETE ⇒ remove a value from a table
  • GRANT ⇒ grant an address permissions to INSERT, UPDATE, or DELETE values
  • REVOKE ⇒ remove permissions from an address
🚧
While rows can be deleted from the table state, row information will remain in the table's history for obvious reasons.

read

Use the read function to query the latest table state. These queries are extremely flexible — simply pass a single parameter of a SQL SELECT statement and get the query results.

Parameters

  • statement ⇒ A read query statement (SELECT ...).

Returns

  • <Array> ⇒ Array of the query response.
    • columns ⇒ Array of column values.
      • <Object> ⇒ Object with column information.
        • name ⇒ Name of the column.
    • rows ⇒ Array of row values.
      • <Array> ⇒ Values for each row returned from the query.

Example

// Run a SQL SELECT query
// @return {ReadQueryResult} Tableland gateway response with row & column values
const { columns, rows } = await tableland.read(`SELECT * FROM ${name};`);

console.log(columns)
// [ { name: 'name' }, { name: 'id' } ]
console.log(rows)
// [ [ 'Bobby Tables', 0 ], [ 'Molly Tables', 1 ] ]

Reference

ReadQueryResult
interface ReadQueryResult<R extends Rows = Array<any>> {
  columns: Columns;
  rows: R;
}

// For context
type Columns = Array<{ name: string }>;
type Rows = Array<string | number | boolean>;

For context, the response from a read query contains a ReadQueryResult object, which is what contains these properties for columns and rows. The columns property contains an enumerated array of column names. The rows property is an array of row-wise table data. Here’s what ReadQueryResult looks like from the example above — in case you choose to choose to avoid destructuring the response from read:

const readRes = await tableland.read(`SELECT * FROM ${name};`);
console.log(readRes)
// {
//	columns: [{ name: 'name' }, { name: 'id' }],
//	rows: [
//		['Bobby Tables', 0],
//		['Molly Tables', 1],
//	],
//}

Note that the rows can easily be iterated over and used to populate frontends, among other use cases:

for (const [rowId, row] of Object.entries(rows)) {
  console.log(`row: ${rowId}`);
  for (const [colId, data] of Object.entries(row)) {
    const { name } = columns[colId];
    console.log(`  ${name}: ${data}`);
  }
}
// row: 0
//   name: Bobby Tables
//   id: 0
// row: 1
//   name: Molly Tables
//   id: 1

resultsToObjects

If that output format does not suit your needs, the Tableland SDK comes with a number of utility functions, including one to convert results to key-value objects:

import { resultsToObjects } from "@tableland/sdk";
// ...

const results = await tableland.read(`SELECT * FROM ${name};`);
const entries = resultsToObjects(results);

for (const { name, id } of entries) {
	console.log(`${name}: ${id}`)
}
// Bobby Tables: 0
// Molly Tables: 1

Before a read query can be made, the table must exist in Tableland, which is dependent on the speed of the base chain’s transaction acceptance. If create and read are in the same logic flow, it is recommended to set up retry logic using receipt, which validates if the create's transaction hash was seen by the Tableland network yet.

🚧
See below for an example of a “wait” logic implementation could look like. Example here

Access Control

Tableland allows developers to set the controller of a table for advanced access controls. The controller can be another address (EOA or contract). Namely, for advanced ACLs, a smart contract must be deployed that implements ITablelandController and then used in the subsequent methods.

setController

Set the controller address for a table.

Only the table owner can set the controller of a table to an address, such as another account address or contract that implements Tableland’s ITablelandTables ACL policy. However, a table with a locked controller can no longer have its controller be set.

Parameters

  • controller ⇒ Address of the controller (wallet / EOA or contract address)
  • name ⇒ Name of the table.
  • options ⇒ Object containing controller method options.
    • rpcRelay ⇒ Boolean to use Tableland nodes to relay on-chain transactions. Defaults to true for testnets and is not possible on mainnets (must be false).

Returns

  • <object> ⇒ Object containing the on-chain tx hash from the contract interaction.
    • hash ⇒ On-chain transaction hash for the setController contract call.

Example

// Pass the address and table name
const tx = await tableland.setController('0x9bA89c8aD3856C0137E268bD76ed12d14696E140','my_sdk_table_80001_311');
console.log(tx)
// {
//   "hash": "0x8abeb09decb1a143b500294601a385c0e80e8c66f0e556245af3f819115fcdb4"
// }
⚠️
Currently, custom table ACLs must be created & deployed as smart contracts. Use tools like hardhat or Ethereum Remix to create & deploy TablelandController contracts. Check Out Remix

getController

Get the current controller address for a table.

If a table has its controller set (e.g., using setController), this method will return the corresponding address via a direct smart contract call. Otherwise, all tables default to the 0x0 address as being the table’s controller.

Parameters

  • name ⇒ Name of the table.

Returns

  • address ⇒ Address of the controller. Defaults to 0x0 address if a controller was never set using setController.

Example

let controller = await tableland.getController("my_sdk_table_80001_311")
console.log(controller)
// 0x0000000000000000000000000000000000000000
// This likely means `setController` was never and defaults to 0x0

lockController

Lock the controller address for a table.

A table can have its controller permanently locked. This can be useful as a final ACL “lock” to ensure the table owner can no longer make any ACL changes (e.g., after some steady state in a production setting). Only the table owner can call this method.

Parameters

  • name ⇒ Table name to permanently lock the controller.

Returns

  • <object> ⇒ Object containing the on-chain tx hash from the contract interaction.
    • hash ⇒ On-chain transaction hash for the lockController contract call.

Example

let tx = await tableland.lockController("my_sdk_table_80001_311")
console.log(tx)
// {
//    hash: "0x084a15643927555e87d3889eaa145bbe382a3687b738b6a7c176d3e5e8fc1cce"
// }

Discoverability

structure

Get all tables with a matching table structure on a specific chain by passing a single parameter: table structure hash. It returns a StructureQueryResult, which is an array of objects that describe the matching tables.

This can be useful for discovering other tables with the same schema, which is represented by an identical structure hash. Note that the tables returned are only those that exist on the same chain.

Parameters

  • structure ⇒ The generated structure hash, which identifies a table’s schema.

Returns

  • <Array> ⇒ Array of matching table objects.
    • <Object> ⇒ Object (StructureQueryResult) defining table information.
      • controller ⇒ Address of the table’s controller.
      • name ⇒ Name of the table.
      • structure ⇒ Structure hash, matching the one passed in the method.

Example

// Get all tables with a matching table structure
const tableStructure = '466dc130f3b02cf995fb66f6a0bdbadc49d2a527c26ac328daddc3f7b8ef779c'
const matchingTables = await tableland.structure(tableStructure)
console.log(matchingTables)
// [
//  	{
//  		controller: "0x4D5286d81317E284Cd377cB98b478552Bbe641ae",
//  		name: "my_sdk_table_80001_311",
//  		structure: "466dc130f3b02cf995fb66f6a0bdbadc49d2a527c26ac328daddc3f7b8ef779c",
//  	},
//  	{
// 	 	  controller: "0xC6ad9738cF5C2B250c4eF95BE247B945d0FBBdAB",
//  		name: "quickstart_80001_96",
// 	 	  structure: "466dc130f3b02cf995fb66f6a0bdbadc49d2a527c26ac328daddc3f7b8ef779c",
//  	}
// ]

schema

Get the schema and constraints for a table by passing a single parameter: table’s name. This can be useful to understand a table’s schema and related SQL types and constraints.

The response is a SchemaQueryResult, which is an object of keys columns (the column’s name, SQL type, and constraints) and the table’s overall table_contraints.

// Get all tables with a matching table structure
// @return {StructureQueryResult} Information about matching tables
const name = 'my_sdk_table_80001_311'
const tableSchema = await tableland.schema(name)
console.log(tableSchema)
// {
// 	columns: [
// 		{
// 			name: "id",
// 			type: "int",
// 			constraints: [],
// 		},
// 		{
// 			name: "name",
// 			type: "text",
// 			constraints: [],
// 		},
// 	],
// 	table_constraints: ["PRIMARY KEY (id)"],
// }
SchemaQueryResult
interface SchemaQueryResult {
  columns: SchemaColumns;
  table_constraints: string[];
}

Additional Helpers & Utilities

receipt

The receipt method can be very useful when combined with the responses from create and write. As a rule of thumb, these two queries interact with the on-chain smart contract, so they will always have a transaction returned from the base chain. Simply pass this transaction hash as a single argument.

The ReceiptResult response will initially be undefined until the Tableland network sees the transaction is included on a block at the target chain. It isn’t required to know how this works, but the implication is that different chains will have different response times for how quickly a transaction is included.

Use receipt to verify if a table has been created before running a write or read query, or if a write query has been confirmed by the network yet.

Parameters

  • txnHash ⇒ On-chain transaction hash to verify its confirmation.

Returns

  • <Object> ⇒ Object on-chain information at the transaction hash.
    • chainId ⇒ The corresponding chain’s id.
    • txnHash ⇒ The corresponding transaction hash.
    • blockNumber ⇒ Block number the transaction was included in.
    • tableId ⇒ ERC721 token identifier.
    • error ⇒ If there’s no error, an empty string; if there is an error occurred, the message is included (e.g,, db query execution failed (code: SQLITE_cannot…)
    • errorEventIdx ⇒ Returns -1 if no error message was included, or returns a number that’s not -1 if an error does exist

Example

// Retrieve on-chain data from tx hash
// @return {ReceiptResult} 
const receiptRes = await tableland.receipt('0xf1aef7e64e941de64551978e72da24072bc5497aebe257b2383d697d7a7a9b87')
console.log(receiptRes)
// {
//    chainId: 80001,
//    txnHash: '0xf1aef7e64e941de64551978e72da24072bc5497aebe257b2383d697d7a7a9b87',
//    blockNumber: 7160814,
//    tableId: '74'
//    "error": "",
//    "errorEventIdx": -1
// }
ReceiptResult
interface ReceiptResult {
	// Target chain's id
  chainId: number;
	// On-chain transaction hash (matching what was originally passes to `hash`)
  txnHash: string;
	// Target chain block in which `txnHash` 
  blockNumber: number;
  error?: string;
}

siwe

This function takes no parameters and simply returns the signer’s SIWE token. It can be useful for frontend apps that, for example, use MetaMask to prompt a user to connect & SIWE.

Parameters

N/A

Returns

  • <Object> ⇒ Object (Token) containing the SIWE token.
    • token ⇒ The signed SIWE token.

Examples

// Retrieve the SIWE token generated by the signer
// @return {Token} Signer's SIWE token string
const signersToken = await tableland.siwe()
console.log(signersToken)
// {
//    token: 'eyJtZXN...'
// }

Under the hood, siwe is actually calling userCreatesToken.

If the connection that was established with connect has rpcRelay set to false It could potentially get away without ever calling siwe().

userCreatesToken

To allow for signer generated SIWE tokens, developers can leverage the userCreatesToken utility top-level function. It takes two parameters — a signer and a chainId — and returns a signed SIWE token:

import { userCreatesToken } from "@tableland/sdk";
// ... where `tableland` was set to the return value from `connect`

const userToken = await userCreatesToken(tableland.signer, 5)
console.log(userToken)
// {
//    token: 'eyJtZXN...'
// }

Development Reference

Supported Chains

When using the SDK, there exists a top-level export called SUPPORTED_CHAINS, which can be imported and used to access Tableland deployment/config information:

import { SUPPORTED_CHAINS } from "@tableland/sdk";
// ... access chain config information for localhost or Polygon Mumbai
const localTestnet = SUPPORTED_CHAINS.custom
const polygonTestnet = SUPPORTED_CHAINS['polygon-mumbai']

Namely, the SUPPORTED_CHAINS object contains the following, which can be helpful when passing parameters to the Tableland connect function (among others), leveraging the ethers library in some capacity, or using a custom hardhat development environment (e.g., localhost):

{
	// Mainnet
	ethereum: {
    name: "ethereum",
    phrase: "Ethereum Mainnet",
    chainId: 1,
    contract: evm.proxies.ethereum,
    host: "https://testnet.tableland.network",
    rpcRelay: false,
  },
  optimism: {
    name: "optimism",
    phrase: "Optimism Mainnet",
    chainId: 10,
    contract: evm.proxies.optimism,
    host: "https://testnet.tableland.network",
    rpcRelay: false,
  },
  polygon: {
    name: "matic",
    phrase: "Polygon Mainnet",
    chainId: 137,
    contract: evm.proxies.polygon,
    host: "https://testnet.tableland.network",
    rpcRelay: false,
  },
	// Testnet
  "ethereum-goerli": {
    name: "goerli",
    phrase: "Ethereum Goerli",
    chainId: 5,
    contract: evm.proxies["ethereum-goerli"],
    host: "https://testnet.tableland.network",
    rpcRelay: true,
  },
  
  "optimism-kovan": {
    name: "optimism-kovan",
    phrase: "Optimism Kovan",
    chainId: 69,
    contract: evm.proxies["optimism-kovan"],
    host: "https://testnet.tableland.network",
    rpcRelay: true,
  },
  "optimism-goerli": {
    name: "optimism-goerli",
    phrase: "Optimism Goerli",
    chainId: 420,
    contract: evm.proxies["optimism-goerli"],
    host: "https://testnet.tableland.network",
    rpcRelay: true,
  },

  "arbitrum-goerli": {
    name: "arbitrum-goerli",
    phrase: "Arbitrum Goerli",
    chainId: 421613,
    contract: evm.proxies["arbitrum-goerli"],
    host: "https://testnet.tableland.network",
    rpcRelay: true,
  },
  "polygon-mumbai": {
    name: "maticmum",
    phrase: "Polygon Mumbai",
    chainId: 80001,
    contract: evm.proxies["polygon-mumbai"],
    host: "https://testnet.tableland.network",
    rpcRelay: true,
  },
  // staging
  "optimism-kovan-staging": {
    name: "optimism-kovan",
    phrase: "Optimism Kovan",
    chainId: 69,
    contract: evm.proxies["optimism-kovan-staging"],
    host: "https://staging.tableland.network",
    rpcRelay: true,
  },
  "optimism-goerli-staging": {
    name: "optimism-goerli",
    phrase: "Optimism Goerli",
    chainId: 420,
    contract: evm.proxies["optimism-goerli-staging"],
    host: "https://staging.tableland.network",
    rpcRelay: true,
  },
  "local-tableland": {
    name: "localhost",
    phrase: "Local Tableland",
    chainId: 31337,
    contract: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
    host: "http://localhost:8080",
    rpcRelay: true,
  },
  // Testing
  custom: {
    name: "localhost",
    phrase: "Custom Chain",
    chainId: 31337, // Default to using hardhat chainId
    // If building locally you can put your contract address and host here or use the contract connection option
    contract: "",
    host: "",
    rpcRelay: true,
  },
}

That wraps up the JavaScript SDK walkthrough!

⚠️
If you experienced any issues or have ideas on what to add to these docs, feel free to open an issue on GitHub or let us know on Discord by posting in #dev-chat! Hop into Discord | Open an Issue

← Previous

Next →