Writing a Template
This Step By Step guide uses an example Template as a reference and describes each component within.
The goal is to demonstrate various ways to integrate balance sheets and value transfer mechanics in a Template.
To understand more about the SDK, please check the Core Concepts section and SDK section
Step 1 - Template skeleton
We start simply by creating a empty Template which we will fill out as we go.
from luna import Template
class BSATemplate(Template):
pass
We declare an empty class BSATemplate
of type Template
.
Everything we add under this class will be part of the Template.
Step 2 - Define roles
Now that we have our empty Template, we'll start by specifying Roles.
A Role
in Luna specifies one or more Participants taking part in a Contract.
In our example, we will have 2 Roles:
lender
, the Participant who sends value.borrower
, the Participant who receives value.
We specify Roles as follows:
from luna import Role
lender = Role()
borrower = Role()
At runtime we will have to specify which Participant takes on which Role. Those Roles can now be used throughout the Template. Referring to a Role will implicitly refer to the Participant holding that Role in the given Contract. Note that we may have different Participants for the same Roles in different Contracts.
Step 3 - Define parameters
Now that we have defined who takes part in our Contract, we'll specify what data we require.
A Parameter
in Luna is used to specify data which must be provided when creating a new Contract.
We will be asking the borrower to supply their asset account through a Parameter. This can be done as follows:
from luna import Parameter
from luna.accounting.types import BalanceSheetAccount
borrower_asset_bsa = Parameter(type_=BalanceSheetAccount, description="The borrower's asset BSA")
This means that when spawning a new Contract, the borrower's asset BSA will have to be supplied.
Step 4 - Define variables
Next up we'll define a Variable.
A Variable
in Luna is used to persist data internally in a Contract.
Unlike a Parameter
, a Variable
is created by the Contract and maintained by the Contract itself. It is not supplied by an external actor.
Here we'll use it to create an Equity Balance Sheet Account (BSA) for the borrower automatically at runtime.
from luna import Parameter, BalanceSheetAccount
borrower_equity_bsa = Variable(type_=BalanceSheetAccount | None, value=None)
We use a union type BalanceSheetAccount | None
, meaning the Variable can be a BalanceSheetAccount
or None
.
The initial value is set to None
. We will set the value to a new BSA using a Clause.
Step 5 - Define clauses
The data input has now been defined, we're ready to define the business logic.
Initialization
We'll start by creating a Clause to initialize our Contract at runtime.
More specifically, we need to create the borrower's equity BSA (remember the Variable we set to None
), as well as the lender's accounts.
We'll create a Clause for each as follows:
from luna import clause, Context
from luna.accounting.types import (
BalanceSheetAccountType,
BalanceSheetUnit,
)
from luna.functions import (
create_balance_sheet_account,
create_default_payment_accounts,
)
@clause(on_init=True)
def create_lender_accounts(self, ctx: Context):
create_default_payment_accounts(
ctx=ctx, unit=BalanceSheetUnit.GBP, participant=self.lender
)
@clause(on_init=True)
def create_borrower_accounts(self, ctx: Context):
self.borrower_equity_bsa = create_balance_sheet_account(
ctx=ctx,
name="BorrowerEquityAccount",
account_type=BalanceSheetAccountType.EQUITY,
code="10002",
unit=BalanceSheetUnit.USD,
participant=self.borrower,
)
The first Clause uses the handy create_default_payment_accounts
Function which sets up a basic and default accounting structure for a given Participant.
This is useful if we just want to make transfers without thinking too much about the accounting logic behind.
The second Clause sets up the account in a more manual way by using create_balance_sheet_account
. We need to supply more information but this also allows for more advanced accounting use cases.
Notice how we specify that we're creating an equity account.
Those two Clauses specify on_init=True
which means they will only run once when the Contract is created.
Business logic
Now that we have our data and initialization logic, we can implement the business logic.
We'll create two Clauses:
- One to do a simple transfer between the two Participants. This way of doing a transfer makes a bunch of assumptions regarding the accounting structure but is easy to use.
- A more specific transfer where we create the accounting entries manually. This is more convoluted but also more powerful and customisable.
@clause(occurrences=1)
def do_simple_transfer(self, ctx: Context):
simple_transfer(
ctx=ctx,
src_participant=self.lender,
dst_participant=self.borrower,
amount=Decimal(100),
unit=BalanceSheetUnit.GBP,
)
This Clause will run only once since we specify occurrences=1
.
It does a simple transfer between the lender and the borrower of GBP100
.
@clause(occurrences=1)
def do_specific_transfer(self, ctx: Context):
value_datetime = datetime.now()
test_event = create_balance_sheet_event(ctx=ctx, value_datetime=value_datetime)
entry_data = [
BalanceSheetEntry(
amount=Decimal(100),
balance_sheet_account=self.borrower_asset_bsa,
balance_sheet_event=test_event,
value_datetime=value_datetime,
),
BalanceSheetEntry(
amount=Decimal(100),
balance_sheet_account=self.borrower_equity_bsa,
balance_sheet_event=test_event,
value_datetime=value_datetime,
),
]
create_balance_sheet_entries(ctx=ctx, entry_data=entry_data)
This final Clause creates actual accounting entries in order to setup the transfer.
The entries must be created under a BalanceSheetEvent
which serves to group entries together.
Final Template
This is our final Template:
from datetime import datetime
from decimal import Decimal
from luna import Template, Variable, Parameter, Context, clause, Role
from luna.types import BalanceSheetAccount
from luna.accounting.types import (
BalanceSheetEntry,
BalanceSheetAccountType,
BalanceSheetUnit,
)
from luna.functions import (
create_balance_sheet_event,
create_balance_sheet_entries,
create_balance_sheet_account,
create_default_payment_accounts,
simple_transfer,
)
class BSATemplate(Template):
borrower_asset_bsa = Parameter(type_=BalanceSheetAccount, description="The borrower's asset BSA")
borrower_equity_bsa = Variable(
type_=BalanceSheetAccount | None,
value=None,
)
lender = Role()
borrower = Role()
@clause(on_init=True)
def create_lender_accounts(self, ctx: Context):
create_default_payment_accounts(
ctx=ctx, unit=BalanceSheetUnit.GBP, participant=self.lender
)
@clause(on_init=True)
def create_borrower_accounts(self, ctx: Context):
self.borrower_equity_bsa = create_balance_sheet_account(
ctx=ctx,
name="BorrowerEquityAccount",
account_type=BalanceSheetAccountType.EQUITY,
code="10002",
unit=BalanceSheetUnit.USD,
participant=self.borrower,
)
@clause(occurrences=1)
def do_simple_transfer(self, ctx: Context):
simple_transfer(
ctx=ctx,
src_participant=self.lender,
dst_participant=self.borrower,
amount=Decimal(100),
unit=BalanceSheetUnit.GBP,
)
@clause(occurrences=1)
def do_specific_transfer(self, ctx: Context):
value_datetime = datetime.now()
test_event = create_balance_sheet_event(ctx=ctx, value_datetime=value_datetime)
entry_data = [
BalanceSheetEntry(
amount=Decimal(100),
balance_sheet_account=self.borrower_asset_bsa,
balance_sheet_event=test_event,
value_datetime=value_datetime,
),
BalanceSheetEntry(
amount=Decimal(100),
balance_sheet_account=self.borrower_equity_bsa,
balance_sheet_event=test_event,
value_datetime=value_datetime,
),
]
create_balance_sheet_entries(ctx=ctx, entry_data=entry_data)
Balance Sheet Accounts
This section provides more details on how accounts and payments work.
Creating accounts
There are two ways for the Template to create Balance Sheet Accounts:
-
create_default_payment_accounts
This Function will create two default accounts for the provided Participant. It will create an
ASSET
andEQUITY
account with codes10001
and10002
respectively. These are considered the default accounts required to make or receive simple payments. If these accounts already exist no actions will be taken. -
create_balance_sheet_account
This Function allows for the creation of a single Balance Sheet Account. It's more verbose than
create_default_payment_accounts
and allows the author to specify the finer details. This Function will also create a Balance Sheet if none already exists for given Participant.
Injecting accounts
There are two ways to inject a Balance Sheet Account into a Contract:
-
Parameter
A Balance Sheet Account can be defined as a Parameter with type
BalanceSheetAccount
. The ID of required Balance Sheet Account can then be passed in as a Parameter during Contract creation. -
Variable
A Balance Sheet Account can be defined as a Variable. The default value will be None which can then be redefined within a Clause during the runtime of a Contract as desired.
Simple Payments
The simple_transfer
Function represents the easiest way to define a simple transfer between two Participants. This Function will expect each Participant to have the default accounts ASSET
and EQUITY
with codes 10001
and 10002
to exist, as per create_default_payment_accounts
function. These accounts will then be used to process the transfer. A single Balance Sheet Event will be create and four Balance Sheet Entries will be created within that Event. These four Entries will be:
src_participant
| ASSET | -100src_participant
| EQUITY | -100dst_participant
| ASSET | 100dst_participant
| EQUITY | 100
Specific Accounting Event And Entry Creation
There are two SDK functions that allow for the fine grained creation of Balance Sheet Events and Entries. These are:
-
create_balance_sheet_event
This Function creates a single event using the arguments provided.
-
create_balance_sheet_entries
This Function will create Balance Sheet Entries in Bulk. These Entries will be validated by the calculation
ASSET = EQUITY + LIABILITY
. So each Entry against anASSET
account will need to be Balanced by corresponding entries toEQUITY
and/orLIABILITY
accounts. This calculation will group Entries by Participant, Event, Unit and Time.