About
Protocol
Walkthroughs
Integrations
Intro to NFT Metadata
Tutorials
Smart Contracts
Concepts
Playbooks
Learn
JavaScript SDK (Legacy)
API Reference for the integrated JavaScript / TypeScript SDK (v3)—this version or earlier will no longer be maintained.
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!
Setup
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).
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:
connect
⇒ establish a connection with a Tableland and the desired chaincreate
⇒ pass a schema to create a table that’s uniquely identifiable across any chainwrite
⇒ insert or update values in the table (INSERT
,UPDATE
,DELETE
,GRANT
,REVOKE
)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 topolygon-mumbai
orsigner.provider.getNetwork
, if available.- See the section at the bottom for how to use other chains with the
SUPPORTED_CHAINS
import. 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 betrue
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" });
// 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" });
// 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
.
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 ReferenceRelays 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
.
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 typicalCREATE 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 tofalse
, meaning, the return will happen upon tx confirmation; iftrue
, returns immediately.timeout
(optional) ⇒ Timeout for waiting until a transaction has been materialized by the Tableland network (usingwaitConfirm
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'sid
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 chainid
(e.g., `80001` means Polygon Mumbai).txnHash
⇒ Transaction hash created from calling the smart contract.blockNumber
⇒ The block thetxnHash
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
,int
,real
,text
,blob
,any
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:
- The table naming convention is SQL compliant (e.g., does not start with numbers)
- The appended
chainId
is supported (e.g.,80001
representspolygon-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.
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 tofalse
.rpcRelay
(optional) ⇒ Boolean to use Tableland nodes to relay on-chain transactions. Defaults totrue
for testnets and is not possible on mainnets (must befalse
).
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"
// }
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 tableDELETE
⇒ remove a value from a tableGRANT
⇒ grant an address permissions toINSERT
,UPDATE
, orDELETE
valuesREVOKE
⇒ remove permissions from an address
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.
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 totrue
for testnets and is not possible on mainnets (must befalse
).
Returns
<object>
⇒ Object containing the on-chain tx hash from the contract interaction.hash
⇒ On-chain transaction hash for thesetController
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"
// }
TablelandController
contracts.
Check Out RemixgetController
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 to0x0
address if a controller was never set usingsetController
.
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 thelockController
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://testnets.tableland.network",
rpcRelay: false,
},
optimism: {
name: "optimism",
phrase: "Optimism Mainnet",
chainId: 10,
contract: evm.proxies.optimism,
host: "https://testnets.tableland.network",
rpcRelay: false,
},
polygon: {
name: "matic",
phrase: "Polygon Mainnet",
chainId: 137,
contract: evm.proxies.polygon,
host: "https://testnets.tableland.network",
rpcRelay: false,
},
// Testnet
"ethereum-goerli": {
name: "goerli",
phrase: "Ethereum Goerli",
chainId: 5,
contract: evm.proxies["ethereum-goerli"],
host: "https://testnets.tableland.network",
rpcRelay: true,
},
"optimism-kovan": {
name: "optimism-kovan",
phrase: "Optimism Kovan",
chainId: 69,
contract: evm.proxies["optimism-kovan"],
host: "https://testnets.tableland.network",
rpcRelay: true,
},
"optimism-goerli": {
name: "optimism-goerli",
phrase: "Optimism Goerli",
chainId: 420,
contract: evm.proxies["optimism-goerli"],
host: "https://testnets.tableland.network",
rpcRelay: true,
},
"arbitrum-goerli": {
name: "arbitrum-goerli",
phrase: "Arbitrum Goerli",
chainId: 421613,
contract: evm.proxies["arbitrum-goerli"],
host: "https://testnets.tableland.network",
rpcRelay: true,
},
"polygon-mumbai": {
name: "maticmum",
phrase: "Polygon Mumbai",
chainId: 80001,
contract: evm.proxies["polygon-mumbai"],
host: "https://testnets.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!
#dev-chat
!
Hop into Discord | Open an Issue← Previous
Next →
On this page
- JavaScript SDK (Legacy)
- Setup
- Install
- Fees
- Overview
- Client-Side Apps
- Node.js
- Import
- require
- Connecting to Tableland
- connect
- Parameters
- Returns
- Example
- Relays on Testnets, Not Mainnets
- Creating Tables
- create
- Parameters
- Returns
- Example
- Table Names
- Schema Validation with hash
- Listing Tables by Address
- list
- Parameters
- Returns
- Example
- Querying Tables
- write
- Parameters
- Returns
- Example
- read
- Parameters
- Returns
- Example
- Reference
- resultsToObjects
- Access Control
- setController
- Parameters
- Returns
- Example
- getController
- Parameters
- Returns
- Example
- lockController
- Parameters
- Returns
- Example
- Discoverability
- structure
- Parameters
- Returns
- Example
- schema
- Additional Helpers & Utilities
- receipt
- Parameters
- Returns
- Example
- siwe
- Parameters
- Returns
- Examples
- userCreatesToken
- Development Reference
- Supported Chains