Plutus Partial Tx
A library to export partial transactions (unbalanced, unsigned) and get them
signed and submitted by a frontend PAB. This enables you to re-use your regular
Haskell Contract
s while still having a deployment solution for production -
effortlessly.
Highlights
- Write all your contracts in the familiar
Contract
monad. - Leverage excellent Haskell-side testing solutions for your
Contract
s, using
Plutip, Contract models etc. - Run your contracts on the testnet (and mainnet!) utilizing a lightweight
frontend PAB (i.e Lucid) - without ever
having to rewrite your contracts or any extra logic. Simply re-use your
Haskell contracts, call
.sign
and .submit
on the frontend. - Extremely lightweight, easy to test in local development environment. If using
bot-plutus-interface
(BPI), all you need is
cardano-node
and plutus-chain-index
.
Usage
Backend environment setup
-
Add the proper versions of bot-plutus-interface
and plutus-partial-tx
as dependencies to your project.
-
Have the proper versions (usually latest) of cardano-node
, cardano-cli
, and a corresponding version (same one used by bot-plutus-interface) of plutus-chain-index
in $PATH
.
-
Have cardano-node
and chain-index
running in the background and properly synced. You may simply copy over the provided testnet
directory and use the scripts there to set up for testnet. Remember to create the db
directory inside testnet/chain-index
first!
Usually, if using nix, plutus-chain-index
is already included by BPI. All you have to do is make it available in the nix shell by adding project.hsPkgs.plutus-chain-index.components.exes.plutus-chain-index
to nativeBuildInputs
.
NOTE: The provided example is using the preview testnet.
NOTE: You need to use a vasil compliant version of BPI. Mainline BPI does not support vasil yet. Use the gergely/vasil
branch instead.
Frontend environment setup
How you set up the frontend is entirely upto you, as long as it can query the Haskell server to obtain a PartialTx
- and use it with lucid-cardano
and lucid-cardano-partialtx
, it's enough.
See Berry-Pool/lucid
for adding Lucid to your dependency.
NOTE: You need to use a vasil compliant version of Lucid. Mainline Lucid does not support vasil yet. Use the vasil
branch instead.
For lucid-cardano-partialtx
(provided by this repo), there are three ways to import it:
Node.js
Install the npm package:
npm install lucid-cardano-partialtx
Import in your file:
import { mkPartialTxInterpreter } from "lucid-cardano-partialtx";
Aside: You can use webpack or similar to bundle your Node project to run on the browser. See full example that does this
Deno
Simply import from deno.land:
import { mkPartialTxInterpreter } from "https://deno.land/x/lucid_partialtx@0.1.3/mod.ts";
Aside: You can use ESBuild or similar to bundle your Deno project to run on the browser. However, you should to replace the deno.land
imports with the browser package url if running in a browser environment. See lucid-partialtx/build.ts that does something similar (but only after generating a node package).
Browser JS
<script type="module">
import { mkPartialTxInterpreter } from "https://unpkg.com/lucid-cardano-partialtx@0.1.3/web/mod.js";
</script>
Aside: This is pure JS directly running in the browser: probably not too practical for large projects.
Haskell server
Once you have the environment set up, your haskell server merely has to use BPI (bot-plutus-interface) to run your contracts returning PartialTx
s. You can hook up BPI with the testnet quite easily: simply copy over the BPI.Testnet.Setup
module from the example.
A simple servant server, showcasing a simple Contract
usage, can be found in example/Main.
Frontend Lucid
All you have to do is create a PartialTxInterpreter
by passing your Lucid
instance to mkPartialTxInterpreter
, make an API call to receieve the PartialTx
from your server, and pass it through the interpreter. The resulting Lucid Tx
can be modified further or directly signed, and submitted.
See example/frontend/src/index.ts.
Full example and how to run it
There is a full example with servant, BPI, and Lucid that can run a dummy
minting contract on the testnet. Check the haskell code
in the example directory. The gist is that you can copy over
BPI.Testnet.Setup
and use the exposed interface to create PartialTx
s in the
context of the testnet.
The example frontend is in example/frontend
.
To run the project and run stuff on the testnet, head inside the nix shell by
doing nix develop
, and follow these steps:
1. Fill in blockfrost config
You need to fill a config.json
and put it in example/frontend/config.json
, this
config should contain your blockfrost API access key:
{
"blockfrostUrl": "TESTNET_BLOCKFROST_URL",
"blockfrostProjId": "TESTNET_BLOCKFROST_PROJID"
}
Aside: Of course, you can also deploy all this in production by switching out
to connect to the mainnet in the BPI setup and blockfrost setup.
2. Start cardano-node
and plutus-chain-index
in background
You'll also need cardano-node
and chain-index
running in the background,
properly connected to testnet.
Unless the directory testnet/chain-index/db
already exists, you should create an empty directory: mkdir testnet/chain-index/db
.
All you have to do run make services
.
This will take some time to sync. You can see the node logs in
testnet/node.log
and chain index logs in testnet/cix.log
. You can query the
node sync progress by running make query-tip
.
Note: Remember to stop these background services when you're done! Use
make stop-services
to do so.
3. Build the frontend project
Firstly, run make build-lucid-lib
at the root project directory.
Now, head inside the example/frontend
directory and run the following commands:
npm i
- to install all the npm dependenciesnpx webpack
- to build the project
Alternatively, if you've already done npm i
and have the node_modules
from
it - you can run make build-frontend
from the root project path.
4. Start the server
Once the node has synced and all the previous steps have been completed, run
make serve
. Head to localhost:8080
to see a beautiful frontend with a
singular dummy minting button!
Note: You'll need a wallet supporting Vasil for signing and submission to
work properly. In particular, mainline Nami does not support vasil yet. You need to use the vasil
branch of nami.
API
Here's the core idea:
Keep your Haskell contracts as they are, just make them return UnbalancedTx
using
Ledger.Constraints.mkTx
.
Alternatively, make them return the ScriptLookups
and the TxConstraints
.
Use either of the two functions provided within this repo to create a
PartialTx
:
import Plutus.Contract.PartialTx
mkPartialTx ::
( FromData (DatumType a)
, ToData (DatumType a)
, ToData (RedeemerType a)
) =>
ScriptLookups a ->
TxConstraints (RedeemerType a) (DatumType a) ->
Either MkTxError PartialTx
unbalancedToPartial :: UnbalancedTx -> PartialTx
Aside: For your Haskell side tests (Plutip or EmulatorTrace), you'd still want
to submit the UnbalancedTx
- which you can still do using
submitUnbalancedTx
.
So calling mkTx
followed by submitUnbalancedTx
is effectively the same as
just calling submitTx
(and similar).
In the frontend, bind your Lucid
instance to mkPartialTxInterpreter
, and use the resulting PartialTxInterpreter
to interpret a PartialTx
:
type PartialTxInterpreter = (x: PartialTx) => Tx;
mkPartialTxInterpreter(lucid: Lucid): PartialTxInterpreter;
Once you obtain a Lucid Tx
, it's as simple as signing and submitting it, which
looks like:
const signedTx = await tx.sign().complete();
return signedTx.submit();