Viết một ứng dụng Plutus cơ bản trong Plutus Playground¶
Plutus apps alà các chương trình chạy ngoài chuỗi và quản lý các phiên bản hợp đồng đang hoạt động. Họ giám sát chuỗi khối, yêu cầu thông tin đầu vào của người dùng và gửi các giao dịch lên chuỗi khối. Nếu bạn là tác giả hợp đồng, xây dựng ứng dụng Plutus là cách dễ nhất để tạo và sử dụng các đầu ra của tập lệnh Plutus. Trong hướng dẫn này, bạn sẽ viết một ứng dụng Plutus để khóa một số ada trong đầu ra tập lệnh và chia chúng đồng đều giữa hai người nhận.
Xác định các loại¶
Bạn bắt đầu bằng cách xác định một số kiểu dữ liệu mà bạn sẽ cần cho ứng dụng Split .
SplitData
mô tả hai người nhận tiền và tổng số tiền được biểu thị bằng ada.
Bạn đang sử dụng``PubKeyHash`` oại để xác định người nhận. Khi thanh toán, bạn có thể sử dụng hàm băm để tạo hai đầu ra khóa công khai.
Phiên bản cho các loại dữ liệu¶
SplitData``này có các phiên bản cho một số kiểu chữ.
Những trường hợp này cho phép tuần tự hóa. ``SplitData``các định dạng khác nhau.
``ToJSON
and FromJSON
are needed for JSON serialization.
JSON objects are passed between the frontend (for example, the Playground) and the app instance.
PlutusTx.FromData
and PlutusTx.ToData
are used for values that are attached to transactions, for example as the <redeemer> of a script output.
This class is used by the Plutus app at runtime to construct Data
values.
Finally, PlutusTx.makeLift
is a Template Haskell statement that generates an instance of the PlutusTx.Lift.Class.Lift
class for SplitData
.
This class is used by the Plutus compiler at compile-time to construct Plutus core programs.
Defining the validator script¶
The validator script is the on-chain part of our Plutus app. The job of the validator is to look at individual transactions in isolation and decide whether they are valid. Plutus validators have the following type signature:
d -> r -> ValidatorCtx -> Bool
where d
is the type of the <datum> and r
is the type of the redeemer.
You are going to use the validator script to lock a script output that contains the amount
specified in the SplitData
.
Note
There is an n-to-n relationship between Plutus apps and validator scripts. Apps can deal with multiple validators, and validators can be used by different apps.
In this tutorial you only need a single validator.
Its datum type is SplitData
and its redeemer type is ()
(the unit type).
The validator looks at the ValidatorCtx
value to see if the conditions for making the payment are met:
The validator checks that the transaction, represented by scriptContextTxInfo
, pays half the specified amount to each recipient.
You then need some boilerplate to compile the validator to a Plutus script (see Viết các tập lệnh xác thực cơ bản).
The Ledger.Typed.Scripts.Validators.ValidatorTypes
class defines the types of the validator, and splitValidator
contains the compiled Plutus core code of validateSplit
.
Asking for input¶
When you start the app, you want to ask the sender for a SplitData
object.
In Plutus apps, the mechanism for requesting inputs is called endpoints.
All endpoints that an app wants to use must be declared as part of the type of the app.
The set of all endpoints of an app is called the schema of the app.
The schema is defined as a Haskell type.
You can build a schema using the Endpoint
type family to construct individual endpoint types, and the .\/
operator to combine them.
The SplitSchema
defines two endpoints, lock
and unlock
.
Each endpoint declaration contains the endpoint’s name and its type.
Note that SplitData
has two PubKeyHash
fields for the recipients, whereas the endpoint has two Wallet
fields (the wallet type is used in the Playground as the identity of a simulated agent).
You are going to convert the wallet values to their corresponding public key hashes in the Split
app.
That way, the user can simply identify the recipient by a number and doesn’t have to enter a public key into a text box.
This type of conversion from a nickname to a unique identifier is a common task for Plutus apps.
To use the lock
endpoint in our app, you call the Plutus.Contract.Request.endpoint
function:
endpoint
has a single argument, the name of the endpoint.
The name of the endpoint is a Haskell type, not a value, and you have to supply this argument using the type application operator @
.
This operator is provided by the TypeApplications GHC extension.
Next you need to turn the two Wallet
values into their public key hashes so that you can get the SplitData
value from the input that was supplied by the user.
Note that the Wallet.Emulator.Wallet.mockWalletPaymentPubKeyHash
function and the Wallet.Emulator.Wallet.Wallet
type are only available in the simulated environment used by the Plutus playground and by Plutus tests.
A real Plutus app would use the metadata server or a custom lookup function for such conversions.
Locking the funds¶
With the SplitData
that you got from the user you can now write a transaction that locks the requested amount of ada in a script output.
Using the constraints library that comes with the Plutus SDK you specify a transaction tx
in a single line.
tx = Constraints.mustPayToTheScript s (Ada.toValue amount)
After calling submitTxConstraints
in the next line, the Plutus app runtime examines the transaction constraints tx
and builds a transaction that fulfills them.
The runtime then sends the transaction to the wallet, which adds enough to cover the required funds (in this case, the ada amount specified in amount
).
Unlocking the funds¶
All that’s missing now is the code for retrieving the funds, and some glue to put it all together.
In unlockFunds
you use the constraints library to build the spending transaction.
Here, tx
combines three different constraints.
collectFromScript
takes the script outputs in unspentOutputs
and adds them as input to the transaction, using the unit ()
as the redeemer.
The other two constraints use mustPayToPubKey
to add payments for the recipients.
Deploying the app on the Playground¶
You have all the functions you need for the on-chain and off-chain parts of the app. Every contract in the Playground must define its public interface like this:
The Playground server uses the endpoints
definition to populate the UI (via the schema, in our case SplitSchema
) and to start the simulation.
endpoints
is the high-level definition of our app:
The select
function acts like a choice between two branches.
The left branch starts with lock
and the right branch starts with unlock
.
The app exposes both endpoints and proceeds with the branch that receives an answer first.
So, if you call the lock
endpoint in one of the simulated wallets, it will call lockFunds
and ignore the unlock
side of the contract.
You also need a couple of declarations that generate glue code for the Playground.
mkSchemaDefinitions ''SplitSchema
$(mkKnownCurrencies [])
You also need an additional import at the top of the file.
import Playground.Contract
After that, you can compile the contract and create a simulation. The following action sequence results in two transactions that lock the funds and then distribute them to the two recipients.
Exercise¶
Extract the function that assigns funds to each recipient from
unlockFunds
andvalidateSplit
to reduce redundancy in the codeExtend the contract to deal with a list of recipients instead of a fixed number of 2.