Interact with smart contracts
Create transactions to work with smart contracts.
Lock Assets in Smart Contract
Assets may be reserved in a smart contract by "locking" them at the script's address. The assets can only be subsequently unlocked when certain conditions are met, for example, in the case of making a purchase in a marketplace contract.
In this demo, we will lock selected assets from your wallet in analways succeed
smart contract. Even though it is called "always succeed" because there is no actual "validating" logic, unlocking the assets still requires the correct datum to be supplied. Also note that in practice, multiple assets (both native assets and lovelace) can be sent to the contract in a single transaction.
We need to supply the script address. Luckily Mesh has a handy function to "resolve" (work out) the script address automatically using: Resolve Script Address from the script's CBOR (4e4d01000033222220051200120011
). Here's how it's done:
import { resolvePlutusScriptAddress } from '@meshsdk/core'; import type { PlutusScript } from '@meshsdk/core'; const script: PlutusScript = { code: '4e4d01000033222220051200120011', version: 'V1', }; const scriptAddress = resolvePlutusScriptAddress(script, 0);
To lock assets in this contract, here's the full code:
import { Transaction, Asset } from '@meshsdk/core'; // this is the script address of always succeed contract const scriptAddress = 'addr_test1wpnlxv2xv9a9ucvnvzqakwepzl9ltx7jzgm53av2e9ncv4sysemm8'; const tx = new Transaction({ initiator: wallet }) .sendAssets( { address: scriptAddress, datum: { value: 'supersecret', }, }, [ { unit: "d9312da562da182b02322fd8acb536f37eb9d29fba7c49dc172555274d657368546f6b656e", quantity: "1", }, ], ); const unsignedTx = await tx.build(); const signedTx = await wallet.signTx(unsignedTx); const txHash = await wallet.submitTx(signedTx);
If the transaction is successful, you would usually want to keep a record of the asset's unit
and the datum
used in the transaction, as this information is required to unlock the assets.
Lock assets in smart contract |
---|
Unlock Assets from Smart Contract
To unlock assets locked in a smart contract, you need to create a transaction that supplies the correct datum. In fact, only a transaction with the corrent datum supplied is able to unlock the assets. in the smart contract, which is required for the transaction's input. In our example, we shall also define the unit
of the asset we are searching for so that we can search for the suitable UTxO.
First, let's create a function to fetch the correct input UTxO from the script address. This input UTxO is needed for the transaction builder. Notee that in this demo, we are using KoiosProvider
, but any of the providers which are implemented by Mesgh can be used (see Providers for list).
async function _getAssetUtxo({ scriptAddress, asset, datum }) { const koios = new KoiosProvider('preprod'); const utxos = await koios.fetchAddressUTxOs( scriptAddress, asset ); const dataHash = resolveDataHash(datum); let utxo = utxos.find((utxo: any) => { return utxo.output.dataHash == dataHash; }); return utxo; }
Then, we query the script address for the UTxO that contains the correct data hash:
// fetch input UTXO const assetUtxo = await _getAssetUtxo({ scriptAddress: 'addr_test1wpnlxv2xv9a9ucvnvzqakwepzl9ltx7jzgm53av2e9ncv4sysemm8', asset: 'd9312da562da182b02322fd8acb536f37eb9d29fba7c49dc172555274d657368546f6b656e', datum: 'supersecret', });
Next, we create the transaction. 4e4d01000033222220051200120011
is the script CBOR for the always succeed
smart contract.
// create the unlock asset transaction const tx = new Transaction({ initiator: wallet }) .redeemValue({ value: assetUtxo, script: { version: 'V1', code: '4e4d01000033222220051200120011', }, datum: 'supersecret', }) .sendValue(address, assetUtxo) // address is recipient address .setRequiredSigners([address]);
Lastly, we build and sign the transaction. Note that here we need to set the 'partial sign' parameter to true
.
const unsignedTx = await tx.build(); // note that the partial sign is set to true const signedTx = await wallet.signTx(unsignedTx, true);
Putting it all together, here's the code to unlock assets from smart contract:
async function _getAssetUtxo({ scriptAddress, asset, datum }) { const koios = new KoiosProvider('preprod'); const utxos = await koios.fetchAddressUTxOs( scriptAddress, asset ); const dataHash = resolveDataHash(datum); let utxo = utxos.find((utxo: any) => { return utxo.output.dataHash == dataHash; }); return utxo; } // fetch input UTXO const assetUtxo = await _getAssetUtxo({ scriptAddress: 'addr_test1wpnlxv2xv9a9ucvnvzqakwepzl9ltx7jzgm53av2e9ncv4sysemm8', asset: 'd9312da562da182b02322fd8acb536f37eb9d29fba7c49dc172555274d657368546f6b656e', datum: 'supersecret', }); // get wallet change address const address = await wallet.getChangeAddress(); // create the unlock asset transaction const tx = new Transaction({ initiator: wallet }) .redeemValue({ value: assetUtxo, script: { version: 'V1', code: '4e4d01000033222220051200120011', }, datum: 'supersecret', }) .sendValue(address, assetUtxo) // address is recipient address .setRequiredSigners([address]); const unsignedTx = await tx.build(); // note that the partial sign is set to true const signedTx = await wallet.signTx(unsignedTx, true); const txHash = await wallet.submitTx(signedTx);
Unlock assets in smart contract |
---|
Minting Assets with Smart Contract
In this demo, we will use a Plutus script to mint tokens. This script is designed to always succeed, meaning that anyone can sign and mint tokens with it, as there is no extra validation logic carried out by this script.
Firstly, we create a new PlutusScript
and "redeemer" (Action
):
import { Action, PlutusScript } from '@meshsdk/core'; const script: PlutusScript = { code: plutusMintingScriptCbor, version: 'V2', }; const redeemer: Partial<Action> = { tag: 'MINT', };
You can get the 'always succeed' Plutus script CBOR (to replace plutusMintingScriptCbor
) from this gist.
Next, we define the asset and its metadata, and add the script
(PlutusScript
), redeemer
(Action
), and theasset
(Mint
) to the transaction:
import { AssetMetadata, Mint } from '@meshsdk/core'; const assetMetadata1: AssetMetadata = { "name": "Mesh Token", ... } const asset: Mint = { assetName: 'MeshToken', ... } tx.mintAsset(script, asset, redeemer);
Here is the full code:
import { Transaction } from '@meshsdk/core'; import { AssetMetadata, Mint, Action, PlutusScript } from '@meshsdk/core'; const script: PlutusScript = { code: plutusMintingScriptCbor, version: 'V2', }; const redeemer: Partial<Action> = { tag: 'MINT', }; const tx = new Transaction({ initiator: wallet }); // define asset#1 metadata const assetMetadata1: AssetMetadata = { "name": "Mesh Token", "image": "ipfs://QmRzicpReutwCkM6aotuKjErFCUD213DpwPq6ByuzMJaua", "mediaType": "image/jpg", "description": "This NFT was minted by Mesh (https://meshjs.dev/)." }; const asset1: Mint = { assetName: 'MeshToken', assetQuantity: '1', metadata: assetMetadata1, label: '721', recipient: 'addr_test1vpvx0sacufuypa2k4sngk7q40zc5c4npl337uusdh64kv0c7e4cxr', }; tx.mintAsset( script, asset1, redeemer, ); const unsignedTx = await tx.build(); const signedTx = await wallet.signTx(unsignedTx); const txHash = await wallet.submitTx(signedTx);
Recipients | ||
---|---|---|
Recipient #1 | ||
Inline Datum
It is possible to attach a "datum" (piece of data) inline to a UTxO outputs, which allows us to use the UTxO to hold information which we can then use without having to spend it (as with normal UTxOs). You can learn more from CIP-32.
Here's an example of creating a UTxO with inline datum:
sendAssets( { address: someAddress, datum: { value: 'supersecret', inline: true, }, }, [ { unit: "64af286e2ad0df4de2e7de15f8ff5b3d27faecf4ab2757056d860a424d657368546f6b656e", quantity: "1", }, ], )
As you can see, you simply have to define the datum
field in the Recipient
input parameter, including a (value
) and setting inline
to true
. This works for every transaction endpoint (e.g. sendLovelace()
, sendAssets()
, sendValue()
).
Reference Script
Validation requires access to any scripts that are involved, but adding entire scripts to transactions increases the transaction size and can lead to bloating of the blockchain. A useful solution is to instead store script references in a UTxO, which allows us to later use that UTxO to build future transactions without having to include the entire script. Thus we can reduce the size of the transaction, which may allow us to send (for example) multiple scripts within one transaction without exceeding the maximum transaction size.
sendAssets( { address: someAddress, script: { version: 'V2', code: '...', }, }, [ { unit: "64af286e2ad0df4de2e7de15f8ff5b3d27faecf4ab2757056d860a424d657368546f6b656e", quantity: "1", }, ], )
Simply define the script
as the Recipient
input parameter. This works for every transaction endpoints (e.g.. sendLovelace()
, sendAssets()
, sendValue()
).
Designing Datum
Mesh allows you to freely design the datum structure to suit the plutus smart contract requirements. You can import the Data
type to help you design the datum.
import { resolveDataHash } from '@meshsdk/core'; import type { Data } from '@meshsdk/core';
A string
A datum as simple as just a string, preferably a hex string.
A number
It can also be a number.
An array
Or an array, where each item can be either a string, number, a list, or a map.
A Map
It can also be a map, where both the keys and its values can be a string, number, a list, or a map.
With constructor
Or a datum with a constructor, where alternative
is a number, and fields
is an array.
Using Redeemer
For redeemers in Mesh, you use the type Action
and you only supply the Data
part to construct it.
import { resolvePaymentKeyHash } from '@meshsdk/core'; import type { Data } from '@meshsdk/core';
Designing Redeemer
Similarly to the datum, there is freedom in design to suit any smart contract, but the redeemer needs to be supplied a little differently.
In this example, we represent a redeemer which matches the StartRedeemer
as defined above with the first Used Address
as input:
Supplying the SecondRedeemer
as defined above:
Supplying the EndRedeemer
as defined above:
Transaction construction
Within the transaction, we can include the redeemer within redeemValue
: