Class: CubistORM
This class extends the Cubist class with project-specific contracts and contract factories. For most applications you should use this class over Cubist: it gives you a higher-level, ORM-like interface.
Since this class is generated at build time, the easiest way to describe it is to walk through an example. The overview has one example; here we'll walk through one of our templates—the cross-chain Storage template—and look at the generated code.
If you haven't gone through the Quick Start guide, it's worth doing so now. You might also find the in-depth walkthough of the multich-chain storage app useful, too. We're using this app in the guide.
Let's start by creating a new project:
- TypeScript
- JavaScript
cubist new --type TypeScript --template Storage storageApp
cd storageApp
cubist new --type JavaScript --template Storage storageApp
cd storageApp
Next, we'll install dependencies and build the project:
- npm
- yarn
npm install
cubist build
yarn
cubist build
Recall from the Quick Start guide that this app consists of two
contracts: StorageSender
stores a number and then calls
StorageReceiver.store
to store the number to the StorageReceiver
contract,
too.
The cubist build command, among other things, generates ORM interfaces for these contracts. Here is what those interfaces look like:
- ./build/orm/index.ts
- ./build/orm/index.js
/// Auto-generated by Cubist. Do not edit manually.
import {
Config,
Contract,
ContractFactory,
Cubist,
Target,
} from '@cubist-labs/cubist';
// Export contract types
import type { StorageSender as EthersStorageSender } from '../polygon/types'
export type { StorageSender as EthersStorageSender } from '../polygon/types'
import type { StorageReceiver as EthersStorageReceiver } from '../ethereum/types'
export type { StorageReceiver as EthersStorageReceiver } from '../ethereum/types'
// Export contract and contract factory types
export type StorageSender = Contract<EthersStorageSender>;
export type StorageSenderFactory = ContractFactory<EthersStorageSender>;
export type StorageReceiver = Contract<EthersStorageReceiver>;
export type StorageReceiverFactory = ContractFactory<EthersStorageReceiver>;
// Export targets
export const Avalanche = Target.Avalanche;
export const Ethereum = Target.Ethereum;
export const Polygon = Target.Polygon;
/** Project-specific CubistORM. */
export class CubistORM extends Cubist {
/** Create new project per target
* @param {Config?} config - Optional config (using near otherwise).
*/
constructor(config?: Config) {
super(config);
}
/** Get contract factory for StorageSender
* @return { StorageSenderFactory } - The contract factory.
*/
get StorageSender(): StorageSenderFactory {
return this.getContractFactory('StorageSender');
}
/** Get contract factory for StorageReceiver
* @return { StorageReceiverFactory } - The contract factory.
*/
get StorageReceiver(): StorageReceiverFactory {
return this.getContractFactory('StorageReceiver');
}
}
/// Auto-generated by Cubist. Do not edit manually.
import {
Config,
ContractFactory,
Cubist,
Target,
} from '@cubist-labs/cubist';
// Export targets
export const Avalanche = Target.Avalanche;
export const Ethereum = Target.Ethereum;
export const Polygon = Target.Polygon;
/** Project-specific CubistORM. */
export class CubistORM extends Cubist {
/** Create new project per target
* @param {Config?} config - Optional config (using near otherwise).
*/
constructor(config) {
super(config);
}
/** Get contract factory for StorageReceiver
* @return {ContractFactory} - The contract factory.
*/
get StorageReceiver() {
return this.getContractFactory('StorageReceiver');
}
/** Get contract factory for StorageSender
* @return {ContractFactory} - The contract factory.
*/
get StorageSender() {
return this.getContractFactory('StorageSender');
}
}
This means that your app code doesn't have to use getContractFactory itself; you can just import the ORM and move on:
- TypeScript
- JavaScript
import { CubistORM, } from '../build/orm/index.js';
// Project instance
const cubist = new CubistORM();
export async function inc(val: number): Promise<void> {
const sender = (await cubist.StorageSender.deployed()).inner;
await (await sender.inc(val)).wait(1);
}
import { CubistORM, } from '../build/orm/index.js';
// Project instance
const cubist = new CubistORM();
export async function inc(val) {
const sender = (await cubist.StorageSender.deployed()).inner;
await (await sender.inc(val)).wait(1);
}
This eliminates a bunch of boilterplate code. And, if you're writing your app
in TypeScript, it ensures that the factories StorageSender
and
StorageReceiver
are well typed and that the contracts these factories
return are well typed, too. This means you can't accidentally call the wrong
method on a contract or call a method with an ill-typed value. For example,
calling the inc
method with a boolean
:
await (await sender.inc(true)).wait(1);
results in a type error:
error TS2345: Argument of type 'boolean' is not assignable
to parameter of type 'PromiseOrValue<BigNumberish>'.
The way we enforce this under the hood is by using TypeChain to generate the
type definitions for each contract (and its shims). The imports you see at the
top of the ./build/orm/index.ts
file import these definitions:
// Export contract types
import type { StorageSender as EthersStorageSender } from '../polygon/types'
export type { StorageSender as EthersStorageSender } from '../polygon/types'
import type { StorageReceiver as EthersStorageReceiver } from '../ethereum/types'
export type { StorageReceiver as EthersStorageReceiver } from '../ethereum/types'
CubistORM currently has two important limitations:
Because Cubist contract factories don't simply wrap ethers.js contract factories, the
deploy()
method is not well-typed and just takesany
arguments. We plan to change this in future versions of the SDK.The ethers.js type definitions that TypeChain generates are more permissive than you might want (e.g., you can call the above
inc
method with a string and the type checker won't complain). We plan to explore generating stricter type definitions (to eliminate bugs due to implicit casts); if this is something you need, please reach out on discord or at [email protected].