Developing your own contract

To develop contracts on your own, you need to understand the data flow from inscription -> contract.

A valid inscription looks like

{"p":"lam","op":"call","contract":"proto","function":"transfer","args":["walletB",100]}

The relevant details are

  • contract: identifier of the contract, the filename (bitcoin.ts)

  • function: which function is called, every contract might have multiple functions, and it is only possible to call one at a time

  • args: arbitrary values which are passed through to the function call. The developer defines what is valid and invalid

With this in mind we can read the inscription as

Call the proto.transfer function with walletB, 100 indicating we want transfer 100 proto tokens to walletB

Architecture of a contract

Each contract is written in typescript and needs to adhere to a Contract type

type Contract = {
  activeOn: number;
};

It needs an activeOn property, this tells the protocol at which block number this contract goes active. This is necessary to let all indexers know which contracts are active and prevents executing contracts before they are deployed.

Inputs

Contract Params

Each contract method is executed with this parameters

type ContractParams = {
  metadata: Metadata;
  ecosystem: Ecosystem;
  eventLogger: EventLogger;
  oracle: Oracle;
  args: Array<unknown>;
};

Lets go through them one by one

Metadata

type Metadata = {
  blockNumber: number;
  origin: string; // the EOA address of the initiator
  sender: string; // the caller of the function
  currentContract: string; // the current contract of the function (me)
  timestamp: string;
  transactionHash: string;
};

Metadata provides information about the "world".

All should be really self explanatory.

Important: origin is always the user address, never anything else. sender on the other hand is always the caller of the function, could be another contract

Ecosystem

type Ecosystem = {
  getContractObj: <T extends Contract>(
    contractName: string,
  ) => Promise<WrappedContract<T> | null>;
  redeployContract: <T extends Contract>(
    templateContract: string,
    newContractName: string,
  ) => Promise<WrappedContract<T>>;
};

Ecosystem offers an API to interact with the Lambda Protocol itself.

The getContractObj returns a contract object. This allows you to call other functions of smart contracts. It does some type magic behind the scenes to make it secure.

The redeployContract makes it possible to dynamically deploy contracts based on existing ones. They are their own contract, have their own storage and don't share anything with the current deployment of the templateContract. This allows you to create LP-Tokens for your AMM, or any other use case.

use cases: call the token.transfer method to transfer tokens between 2 users, ...

EventLogger

type EventLogger = {
  log: (event:  {
    type: string;
    message: string;
  }) => Promise<void>;
};

This does not have an immediate benefit to the user, but it is good practice to use events to log out what happened. They are recorded and added to the transaction later on.

use cases: log a transfer of a token, log a successful OTC deal, log a deposit

Oracle

type Oracle = {
  getRawBlock: (blockNumber: number) => Promise<Uint8Array>;
  getRandom: () => SeedableRandom;
};

This allows you to get outside information inside your smart contract functions. Currently it allows to get the raw block data of any block as well as a random object.

use cases: Digital-Matter-Theory with raw block data, finding patterns in the block, random values for nfts, games and so

This is basically all you need to create your own. Check out the public GitHub repository of the available contracts in the protocol to get some inspiration.

Last updated