Get started with blueprint SDK — part 1
Introduction
This article is part 1 of a 2-part tutorial to assist developers in getting started with the blueprint SDK. By the end of the tutorial, you will have developed your first blueprint using the SDK. In this part, you will conceive and design your first blueprint.
Overview of the task
Our tutorial is divided into two parts. In this part (1), we begin by conceiving and designing our blueprint. In part 2, we conclude by implementing (coding) and testing it. To illustrate our tutorial, let’s use a simple swap contract as an example.
Swap contracts allow users to convert one token into another. Suppose we want to create swap contracts on Hathor Network. For this purpose, a blueprint that models such use cases is necessary, from which multiple contracts can be instantiated.
Let’s also suppose that, after evaluating the blueprints available in Hathor platform, we understand that none of them meet our use case, and we decide to develop a new blueprint called "swap".
Sequence of steps
We will represent the process of conceiving and designing our blueprint through a walkthrough divided into this sequence of steps:
- Specify requirements.
- Model contract interactions.
- Model contract state.
- Model contract creation.
- Model contract execution.
- Model execution failures.
Task execution
Now, it's time to get your hands dirty. In this section, we will describe the steps in detail.
Step 1: specify requirements
The swap contracts we want to create should meet the following requirements:
- Each swap contract must operate with exactly two tokens — e.g., tokens A and B.
- Each contract user may request the conversion of either of the two tokens into the other — i.e., converting tokens A for B, or vice versa.
- Both possible conversions will follow the same conversion ratio — e.g., the contract converts tokens A for B at a ratio of 2:1 and tokens B for A at a ratio of 1:2.
- The contract must custody both tokens A and B, and the contract creator must provide initial liquidity for the contract to operate.
- The contract should not provide "change" to users. That is, users must provide an amount of tokens in the exact proportion they wish to convert them into another token.
Step 2: model contract interactions
Regarding contract creation — as with every blueprint — we will need to implement the public method initialize
. Regarding execution, our contracts will offer only one functionality to users — executing swaps. For this, we need to implement a single public method, which we will call swap
.
Regarding readings, initially, it will not be necessary to implement any view methods in our blueprint with the specific purpose of providing information to users. At the end of the next section, after we model the state of our contract, we will be able to confirm this statement.
In summary, we define that our swap blueprint will have two public methods:
initialize
, common to all blueprints, for creating our swap contracts; andswap
, for executing token swaps.
Step 3: model contract attributes
For our swap contracts, we need attributes to store the following information:
- which are the only two tokens with which the contract operates; and
- what the conversion ratio between the tokens is.
Let's model the attributes of our swap blueprint accordingly:
token_a
identifies the first token with which the contract operates.token_b
identifies the second token with which the contract operates.multiplier_a
andmultiplier_b
together define the conversion ratio between the tokens.
Note that the values of these attributes are set during the contract's creation and remain unchanged thereafter. To demonstrate and test the change in the contract's state after each successful execution (and the absense of change after an execution failure), let's also add the attribute swaps_counter
, which should count how many swaps the contract has performed over its lifetime.
Thus, the state of each swap contract created from our blueprint will be composed of the value of:
- Its five attributes:
token_a
token_b
multiplier_a
multiplier_b
swaps_counter
- The balance of its tokens
- The blueprint ID (that will identify the swap blueprint)
Furthermore, we conclude that there is no information that users need about the contract that is not already saved in its state. Therefore, it will not be necessary to implement view methods for user queries.
Step 4: model contract creation
To create a swap contract, a user (wallet or contract) should make a call such as:
blueprint_id
references the swap blueprint;nc_method
callsinitialize
;nc_args
is the set of arguments that will define the swap contract — i.e., four positive integers for values oftoken_a
,token_b
,multiplier_a
, andmultiplier_b
; andactions
should consolidate as deposits of the two tokens with which the contract will operate (token_a
andtoken_b
).
For example, suppose a user wants to create a contract to perform swaps between tokens A and B, with a conversion ratio of 2 tokens A for every 1 token B; and wishes to initially load this contract with two initial deposits of 200 tokens A and 100 tokens B. For this, they will need to make the following call:
blueprint_id
: identifier of swap blueprint on Hathor platform;nc_method
:initialize
;nc_args
:'token_a'='<token_A_UID>', 'token_b'='<token_B_UID>', 'multiplier_a'=2, 'multiplier_b'=1
; andactions
: two deposits of 200 tokens A and 100 tokens B.
The logic of initialize
implemented by us, swap blueprint developers, should perform the following checks:
- Validating the arguments: must validate whether the set of arguments passed accurately denote two distinct and valid tokens on the blockchain; and two positive integers to compose the conversion ratio.
- Validating the deposits: must validate if there are exactly two deposits being made, one for each of the two distinct tokens from the previous validation.
And then this contract will be created with the following initial state:
- Blueprint ID:
swap
blueprint UUID - Balance:
<token_A_UID>
: 200<token_B_UID>
: 100
- Attributes:
token_a
:<token_A_UID>
token_b
:<token_B_UID>
multiplier_a
: 2multiplier_b
: 1swaps_counter
: 0
Step 5: model contract execution
As we defined in step 2, the only reason a user has to execute it, is to utilize the swap functionality. For this, they shoudl make a call such as:
contract_id
references a swap contract;nc_method
callsswap
;nc_args
will be an empty array, as theactions
are sufficient to fully describe the token swap we want to make; andactions
describes a deposit of tokens A and a withdrawal of tokens B, or vice versa.
For example, suppose a user wants to utilize the swap contract created in the previous step to perform the following swap: they want to convert 20 tokens A for 10 tokens B. For this, they will need to make a call such as:
contract_id
: a swap contract UUID;nc_method
:swap
;nc_args
: none; andactions
: deposit of 20 tokens A and a withdrawal of 10 tokens B.
swap
method will verify if the set of actions proposed by the caller complies with the business logic defined in the contract. In our example, the contract will need to ensure that this set actions denotes:
- an exclusive conversion between tokens A and B; and
- the proportion of the token swap proposed by the user matches the ratio defined in the contract.
In our example, the conversion of 20 tokens A for 10 tokens B requested by the user meets the contract's business rules and will therefore pass all the validations.
As the contract call in our example passes all these validations, swap
method will update the swaps_counter
, and will return None
, indicating that the contract execution was a "success". Thus, the new state of the contract (created in the previous step) will be:
- Blueprint ID:
swap
blueprint UUID - Balance:
<token_A_UID>
: 220<token_B_UID>
: 90
- Attributes:
token_a
:<token_A_UID>
token_b
:<token_B_UID>
multiplier_a
: 2multiplier_b
: 1swaps_counter
: 1
Step 6: model execution failures
When a user makes a call to execute a nano contract — calling one of its public methods other than initialize
— what will occur during the method's execution is:
- Update of the contract's attributes.
- Authorization of the set of actions that the user requested, considering the passed arguments and the current state of the contract.
For example, in the case of our swap contracts, what the public method swap
does is:
- Check if the swap request made by the user follows the established business rules.
- Update the
swap_counter
attribute. - Raise an exception to indicate that the execution failed or return nothing (
None
) to indicate that the execution was successful.
Finally, we must model the cases in which the execution of the contract (swap
method call) should fail. The execution of the swap
method should raise an exception in the following scenarios:
- Attempt to deposit or withdraw without a counterpart.
- Attempt to swap involving a token that the contract does not operate with.
- Attempt to swap involving an incorrect conversion ratio.
An attempt to deposit or withdraw without a counterpart (1) means that a user tried to perform an operation that does not characterize a token swap. To be characterized as a swap, the operation must have exactly one deposit and one withdrawal — that is, the user provides something to the contract and receives something in return. It cannot be any different. There cannot be either more or fewer deposits and withdrawals than this. Here are five different examples of requests that the contract should check actions
and consider it invalid:
- Deposit of 10 tokens A.
- Withdrawal of 10 tokens B.
- Deposit of 10 tokens A and deposit of 10 tokens B.
- Withdrawal of 20 tokens A and withdrawal of 20 tokens B.
- Deposit of 20 tokens A, withdrawal of 10 tokens A, and deposit of 30 tokens C.
An attempt to swap involving a token that the contract does not operate with (2) means that a user tried to perform a swap operation where at least one token is different from the two defined at contract creation, which in our case are tokens A and B. Here are four different examples of requests that the contract should check actions
and consider it invalid:
- Deposit of 20 tokens C and withdrawal of 10 tokens B.
- Deposit of 10 tokens C and withdrawal of 10 tokens A.
- Deposit of 2 tokens A and withdrawal of 1 token C.
- Deposit of 1 tokens B and withdrawal of 3 tokens C.
An attempt to swap involving an incorrect conversion ratio (3) means that a user tried to perform a swap operation where the conversion ratio is different from the one defined at the creation of the contract, which in our case is 2:1 between tokens A and B. Here are two different examples of requests that the contract should check actions
and consider it invalid:
- Deposit of 20 tokens A and withdrawal of 30 tokens B.
- Deposit of 20 tokens B and withdrawal of 20 tokens A.
Note that in our design, we have modeled that even in situations where the contract does not (theoretically) lose anything, the contract should fail execution; for example, when a user tries to make a deposit to the contract without receiving anything in return, or when they attempt a swap where they are depositing more tokens to the contract than necessary.
Task completed
With this, we conclude part 1 of our tutorial, in which we conceived and designed the swap blueprint. Proceed to part 2, where we will implement (code) and test it.