Skip to content

Latest commit

 

History

History
99 lines (66 loc) · 4.79 KB

using-from-js.md

File metadata and controls

99 lines (66 loc) · 4.79 KB

Building JavaScript SDKs with CTL

Normally, dApps involve three parts:

  • on-chain logic (Plutus, Plutarch or Aiken scripts)
  • off-chain logic (in our case, implemented using CTL)
  • user interface

Providing CTL-based JavaScript SDKs is the simplest way to connect user interfaces (most commonly, web apps) with off-chain logic. These SDKs expose app-specific APIs for web developers to plug into the user interface. SDKs are normally consumable as NPM packages.

Explore the template or CTL itself for an example setup. See the WebPack config or the esbuild config we provide.

SDK packaging for NodeJS

NodeJS apps do not require to be bundled in order to be run (however, it is possible, see the Makefile options).

An NPM package with main set to the compiled JS entry point (e.g. ./output/ApiModuleName.js) is sufficient, but the runtime dependencies of said package must include the same package versions CTL itself uses in its package.json.

SDK bundling for the browser

SDKs must be bundled to be usable in the browser. We support two bundlers: esbuild and WebPack. There are two options how to approach bundling and packaging:

  1. bundling a CTL-based SDK before consuming it as dependency in the app, i.e. putting the bundled sources in an NPM package. Bundling twice is not a good practice, and it is hard to even make it work, so in case this path is chosen, the developer should ensure that the SDK does not get bundled twice by the second bundler.

  2. [recommended] bundling a CTL-based SDK together with the UI part of the app. This is simpler, but in case a bundler different from esbuild or WebPack is used, problems may arise due to bundler differences. It should be possible to use other bundlers, as long as they support async top-level imports, WebAssembly and browser package.json field.

Defining SDK APIs in PureScript

Developers should start from reading this PureScript guide that shows how to call PureScript from JS.

Suppose we want to wrap a single Contract into an interface to call it from JS with Nami wallet.

We have to expose functions to manage contract environment - initialization and finalization, as well as a config value we will use.

module Api where

import Prelude

import Contract.Config (ContractParams, testnetNamiConfig)
import Contract.JsSdk (mkContractEnvJS, stopContractEnvJS)
import Contract.Monad (ContractEnv, runContractInEnv)
import Control.Promise (Promise, fromAff)
import Data.Function.Uncurried (Fn1, mkFn1)
import Effect.Unsafe (unsafePerformEffect)
import Scaffold (contract) -- our contract

initialize :: Fn1 ContractParams (Promise ContractEnv)
initialize = mkContractEnvJS

finalize :: Fn1 ContractEnv (Promise Unit)
finalize = stopContractEnvJS

run :: Fn1 ContractEnv (Promise Unit)
run = mkFn1 \env ->
  unsafePerformEffect $ fromAff $ runContractInEnv env contract

config  :: ContractParams
config = testnetNamiConfig -- use Nami wallet
  • Fn1 - Fn10 types are wrappers that represent uncurried JavaScript functions with multiple arguments, and mkFn1 - mkFn10 are their constructors.
  • Contract.JsSdk is a module containing synonyms for some Contract.Monad functions, but adapted for use in JS SDKs.
  • fromAff converts Aff a to Effect (Promise a), and unsafePerformEffect removes the Effect wrapper that is not needed on the JS side.

Calling the SDK API

The module above can be imported like this:

import { initialize, config, run, finalize } from 'your-api-package';

(async () => {
    const env = await initialize(config);
    try {
        await run(env);
    } finally {
        await finalize(env);
    }
})();

Notice that we used finally to finalize - this is because a running contract environment would prevent the script from exiting otherwise. Please read this guide for info on how to manage the runtime environment correctly.

See also