Skip to content

Commit

Permalink
Merge pull request #1453 from Plutonomicon/klntsky/js-sdk-docs
Browse files Browse the repository at this point in the history
Add docs & API for JS SDK creation
  • Loading branch information
klntsky authored Mar 13, 2023
2 parents 607be66 + 1e041f0 commit 591e6ae
Show file tree
Hide file tree
Showing 15 changed files with 371 additions and 42 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- New functions in the assertion library to track changes in CIP-30 wallet total balance - see `Contract.Test.Assert` ([#1440](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1440))
- Added `start-runtime` npm script to the template, to simplify UX ([#1440](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1440))
- Configuration options for Kupo in `buildCtlRuntime` ([`deferDbIndexes`](https://cardanosolutions.github.io/kupo/#section/Getting-started/-defer-db-indexes) and [`pruneUtxo`](https://cardanosolutions.github.io/kupo/#section/Getting-started/-prune-utxo)) ([#1448](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1448))
- `Contract.JsSdk` module containing helper functions for developers who want to use CTL from JS. See also: [this new guide](./doc/using-from-js.md) ([#1453](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1453))
- New [docs for contract environment](./doc/contract-environment.md) ([#1453](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1453))

### Changed

Expand All @@ -77,6 +79,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- `Contract.Wallet.Key.publicKeyFromPrivateKey` uses `PublicKey` type from public API ([#1440](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1440))
- `Contract.Test.Blockfrost.executeContractTestsWithBlockfrost` does not require optional `CtlBackendParams` value (it is now constructed from environment variables).
- `plutip-server` was moved from [Plutip repo](https://github.com/mlabs-haskell/plutip) to CTL ([#1415](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1415))
- Default NodeJS stable version is now v18 ([#1453](https://github.com/Plutonomicon/cardano-transaction-lib/pull/1453))

### Fixed

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ Please explore our documentation to discover how to use CTL, how to set up its r
- [CTL's runtime dependencies](./doc/runtime.md)
- [Blockfrost support](./doc/blockfrost.md)
- [Getting started writing CTL contracts](./doc/getting-started.md)
- [Managing contract environment](./doc/contract-environment.md)
- [Using CTL from JS](./doc/using-from-js.md)
- [Importing Plutus Scripts](./doc/importing-scripts.md)
- [Migrating from Plutus to CTL](./doc/plutus-comparison.md)
- [Testing overview](./doc/testing.md)
- [Overview of testing approaches](./doc/testing.md)
- [Testing contracts with Plutip](./doc/plutip-testing.md)
- [End-to-end testing with headless browsers](./doc/e2e-testing.md)
- [Utilities for testing](./doc/test-utils.md)
Expand Down
76 changes: 76 additions & 0 deletions doc/contract-environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [`Contract` environment](#contract-environment)
- [`ContractEnv` management](#contractenv-management)
- [Following the bracket pattern](#following-the-bracket-pattern)
- [Manually passing the environment](#manually-passing-the-environment)
- [See also](#see-also)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

# `Contract` environment

CTL environment (`ContractEnv` type) consists of:

- various settings
- network protocol parameters (fetched once on initialization)
- active Ogmios `WebSocket` (optional)
- CIP-30 wallet connection (optional)
- internal state used by [synchronization primitives](./query-layers.md)

## `ContractEnv` management

Initialization is a costly operation, and having multiple distinct runtimes may lead to problems with state synchronization and excessive `WebSocket` use, so it is recommended to use only one runtime at any point in time.

If only one `Contract` is ever executed, just using `runContract` is perfectly fine. Otherwise, there are two better approaches:

### Following the bracket pattern

[Bracket pattern](https://wiki.haskell.org/Bracket_pattern) in functional programming world is commonly used to safely manage resources. In our case, `withContractEnv` should be used to initialize and finalize a `ContractEnv`. `withContractEnv` should be used to run a `Contract` within the environment:

```purescript
withContractEnv :: forall (a :: Type). ContractParams -> (ContractEnv -> Aff a) -> Aff a
runContractInEnv :: forall (a :: Type). ContractEnv -> Contract a -> Aff a
myContract1 :: Contract Uni
myContract2 :: Contract Uni
myRunner :: ContractParams -> Aff Unit
myRunner params = withContractEnv params \env -> do
runContractInEnv env myContract1
runContractInEnv env myContract2
```

Using bracket functions is safe, but is not always convenient, because `withContractEnv` callback must hold the `Aff` context until all `Contract`s exit.

### Manually passing the environment

Another approach is using `mkContractEnv` coupled with `runContractInEnv` and `stopContractEnv`.

Here's an example:

```purescript
mkContractEnv :: ContractParams -> Aff ContractEnv
stopContractEnv :: ContractEnv -> Aff Unit
myContract1 :: Contract Uni
myContract2 :: Contract Uni
myRunner :: ContractParams -> Aff Unit
myRunner params = do
env <- mkContractEnv
void $ try do
runContractInEnv env myContract1
runContractInEnv env myContract2
stopContractEnv env
```

This approach is less safe in general, and it's fairly easy to hit the max WebSocket connections limit (which is 200 for Firefox) due to a forgotten `stopContractEnv` call (e.g. due to an exception), not to mention that any websocket that is not closed will force the server to also keep the connection.

This approach, however, is better suited for use when creating [custom JavaScript SDKs](./using-from-js.md).

## See also

- [Using CTL from JS](./using-from-js.md)
- [CTL runtime](./runtime.md)
38 changes: 4 additions & 34 deletions doc/ctl-as-dependency.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ CTL can be imported as an additional dependency into a Purescript project built
- [Caveats](#caveats)
- [Using CTL's overlays](#using-ctls-overlays)
- [Upgrading CTL](#upgrading-ctl)
- [Using CTL from JS](#using-ctl-from-js)
- [Bundling](#bundling)
- [Wrapping CTL into a JS interface](#wrapping-ctl-into-a-js-interface)
- [See also](#see-also)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand Down Expand Up @@ -86,35 +84,7 @@ Make sure to perform **all** of the following steps, otherwise you **will** enco

- Sometimes the WebPack configuration also comes with breaking changes. Common source of problems are changes to `resolve.fallback`, `plugins` and `experiments` fields of the WebPack config. Use `git diff old-revision new-revision webpack.config.js` in the root of a cloned CTL repo, or use `git blame`.

## Using CTL from JS
## See also

### Bundling

The recommended way to bundle CTL is to use WebPack.

We depend on WebPack's `DefinePlugin` to conditionally load one of our dependency (`cardano-serialization-lib`) nodejs or browser version.

That means that CTL _requires_ bundling it the same way when used as a dependency, as we do in development. If you intend to use another bundler, something like `DefinePlugin` should be used to transform the imports from

```javascript
let lib;
if (typeof BROWSER_RUNTIME != "undefined" && BROWSER_RUNTIME) {
lib = require("@emurgo/cardano-serialization-lib-browser");
} else {
lib = require("@emurgo/cardano-serialization-lib-nodejs");
}
```

to only one of the variants.

Our default [WebPack config](../webpack.config.js) uses `BROWSER_RUNTIME` environment variable to differentiate between two bundling options.

The reason why we are tied to WebPack is that it is one of the few bundlers that support async WASM imports.

There's [a claim that Vite bundler can also be used](https://github.com/Plutonomicon/cardano-transaction-lib/issues/79#issuecomment-1257036068), although we don't officially support this method.

### Wrapping CTL into a JS interface

Some users may want to provide a small set of APIs behind a JS/TS interface, instead of dealing with PureScript code.

There's nothing specific to be done for CTL (it's a normal PureScript project), so [this PureScript guide](https://book.purescript.org/chapter10.html#calling-purescript-from-javascript) may be of help. Note that our PureScript version is using CommonJS modules and not ES modules.
- [How to use CTL-based apps from JS](./using-from-js.md)
- [Managing Contract environment correctly](./contract-environment.md)
19 changes: 18 additions & 1 deletion doc/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ This document lists common problems encountered by CTL users and developers.
- [Bundling-related](#bundling-related)
- [Q: `lib.something` is not a function, why?](#q-libsomething-is-not-a-function-why)
- [Q: I see `spago: Error: Remote host not found`, why?](#q-i-see-spago-error-remote-host-not-found-why)
- [Q: I see `WebAssembly module is included in initial chunk.` error, why?](#q-i-see-webassembly-module-is-included-in-initial-chunk-error-why)
- [Q: I see `Cannot use 'import.meta' outside a module` error in the browser, why?](#q-i-see-cannot-use-importmeta-outside-a-module-error-in-the-browser-why)
- [Q: I see `Module not found: Error: Can't resolve 'utf-8-validate'` error when bundling, why?](#q-i-see-module-not-found-error-cant-resolve-utf-8-validate-error-when-bundling-why)
- [Common Contract execution problems](#common-contract-execution-problems)
- [Q: What are the common reasons behind BalanceInsufficientError?](#q-what-are-the-common-reasons-behind-balanceinsufficienterror)
- [Q: CTL consumed my collateral](#q-ctl-consumed-my-collateral)
Expand Down Expand Up @@ -49,6 +52,20 @@ URL: https://github.com/purescript/package-sets/releases/download/psc-0.14.5-202

means that the CTL overlay hasn't been properly applied. Add `ctl.overlays.spago`.

### Q: I see `WebAssembly module is included in initial chunk.` error, why?

You may be trying to use `require` instead of `import` in the app entry point, see [here](./using-from-js.md).

### Q: I see `Cannot use 'import.meta' outside a module` error in the browser, why?

`type="module"` is required in the HTML script import, see [here](./using-from-js.md).

Other possible cause may be that you are trying to run a browser-targeted bundle in NodeJS.

### Q: I see `Module not found: Error: Can't resolve 'utf-8-validate'` error when bundling, why?

You probably forgot to set `BROWSER_RUNTIME=1` for the `webpack` command, see [here](./using-from-js.md).

## Common Contract execution problems

### Q: What are the common reasons behind BalanceInsufficientError?
Expand Down Expand Up @@ -128,7 +145,7 @@ If you are under wayland you need to add `--ozone-platform=wayland` to the argum

Use only one `ContractEnv` value. They are implicitly created every time `runContract` is called, so avoid using this function if you need to run multiple `Contract`s.

Instead, initialize the environment with `withContractEnv` and pass it to `runContractInEnv`. The former ensures that the environment is properly finalized, but it forces the developer to follow the bracket pattern, which is not always convenient. As an alternative, `mkContractEnv` can be used. If you are initializing a contract environment with `mkContractEnv` only once during the lifeteime of your app, you should be fine, but if you re-create it dynamically and do not finalize it with `stopContractEnv`, it's fairly easy to hit the max websocket connections limit, which is 200 for Firefox, not to mention that it would be forcing the server to keep the connections.
See [here](./ctl-as-dependency.md#initializing-the-environment) for more info.

### Package 'chromium-105.0.5195.125' is not supported on 'x86_64-darwin'

Expand Down
4 changes: 2 additions & 2 deletions doc/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ main = Contract.Monad.launchAff_ do

### Making the `ContractEnv`

The `ContractEnv` type contains configuration values and websocket connections that are required to execute contracts written in CTL. The users should not construct it directly - `Contract.Config.ContractParams` should be used instead.
The `ContractEnv` type contains data that is required to execute contracts written in CTL. The users should not construct it directly - `Contract.Config.ContractParams` should be used instead.

For local development and testing, we provide `Contract.Config.testnetConfig` where all `CtlBackend` service hosts are set to `localhost` and the `logLevel` is set to `Trace`.

It is **not recommended to directly construct or manipulate a `ContractEnv` yourself** as the process of making a new config initializes websockets. Instead, use `Contract.Monad.ContractParams` with `runContract`.
It is **not recommended to directly construct or manipulate a `ContractEnv` yourself** as the process of making a new config initializes websockets. Instead, use `Contract.Monad.ContractParams` with `runContract` or its variants.

A special `Contract.Config.WalletSpec` type is used to specify which wallet to use during the `Contract` lifetime.

Expand Down
1 change: 1 addition & 0 deletions doc/query-layers.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Thus, the goal of the developers is to ensure that the set of UTxOs available to

- A dApp tries to balance a transaction with UTxOs from the wallet that are not yet available in Ogmios or Blockfrost, causing an error response
- A transaction is passed for signing, but the wallet does not yet know about the UTxOs it spends, and thus refuses to sign it (this happens with Eternl)
- A transaction is sent to the network via Ogmios or Blockfrost and is confirmed there, but the wallet still does not know about its UTxOs.

CTL tries to be smart when dealing with the issue, and it aims to let the user work with both query layers as if it was one. To achieve this guarantee, CTL follows three simple rules:

Expand Down
1 change: 1 addition & 0 deletions doc/secp256k1-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ For a more in depth oversight please see [Cip-49](https://github.com/mlabs-haske
[Cip-49](https://github.com/mlabs-haskell/CIPs/tree/c5bdd66fe49c19c341499f86cebaa2eef9e90b74/CIP-0049) provides two new Plutus builtin functions for signature verification.

Both functions take the following as Parameters:

- A verification key;
- An input to verify (either the message itself, or a hash);
- A signature.
Expand Down
Loading

0 comments on commit 591e6ae

Please sign in to comment.