Skip to content

NPL examples

This page contains examples of NPL code that can be used to build a backend service. Each example is a complete, self-contained NPL application that can be deployed to the NPL Runtime.

Examples do not necessarily include tests, nor claims to instantiate protocols.

Additional resources can be found in the Getting started page.

For more details and explanations, feel free to reach out to the NOUMENA Community. We're happy to help you with your use cases.

Document Editor

A document validation process between an editor and an approver.

This simple example illustrates the use of parties and permissions, the use of parties to control which users can call which permissions, and the use of the @api annotation to generate a REST API. A state machine is used to manage the lifecycle of the protocol instance.

package document;

@api
protocol[editor, approver] Document(
    var content: Text
) {

    initial state drafted;
    final state approved;

    @api
    permission[editor] edit(
        newContent: Text
    ) | drafted {
        content = newContent;
    };

    @api
    permission[approver] approve() | drafted {
        become approved;
    };
};

Hello World

The hello world example illustrates how to extract the username from the JWT token and use it to greet the user.

package demo;

@api
protocol[greeter] HelloWorld() {

    initial state greeting;
    final state greeted;

    @api
    permission[greeter] sayHello() returns Text | greeting {
        become greeted;
        return "Hello " + getUsername(greeter) + "!";
    };
};

function getUsername(party: Party) returns Text ->
    party.entity()
    .getOrNone("preferred_username")
    .getOrFail()
    .toList()
    .get(0);

Find the Hello World example in the npl-init repository.

IOU

A simple IOU protocol between an issuer and a payee, illustrating a debt repayment process.

This examples uses structs to define data objects, enums to define possible values, and a notification to emit events to the payee of the repayment occurrence. Protocol-level and global functions are used to keep the code clean and readable. The use of require to validate the input parameters is used to ensure the protocol instance is in a valid state.

Follow the Programming an IOU tutorial for a step-by-step explanation.

package objects.iou

struct TimestampedAmount {
    amount: Number,
    timestamp: DateTime
};

struct PaymentConfirmation {
    confirmed: Boolean,
    payment: TimestampedAmount
};

function total(entries: List<TimestampedAmount>) -> entries.map(function(p: TimestampedAmount) -> p.amount).sum();

notification RepaymentOccurrence(paymentAmount: Number, remainingAmount: Number) returns Unit;

struct PaymentDetails {
    description: Text,
    forAmount: Number,
    paymentAmount: TimestampedAmount,
    remainingAmount: Number
};

@api
protocol[issuer, payee] Iou(var description: Text, var forAmount: Number) {
    require(forAmount > 0, "Initial amount must be strictly positive");

    initial state unpaid;
    state payment_confirmation_required;
    final state repaid;
    final state forgiven;

    private var payments = listOf<TimestampedAmount>();
    var paymentToBeConfirmed: Optional<TimestampedAmount> = optionalOf<TimestampedAmount>();
    var amountOwed: Number = forAmount;

    function getAmountOwed() returns Number -> forAmount - total(payments);

    @api
    permission[issuer] pay(amount: Number) | unpaid {
        require(amount > 0, "Amount must be strictly positive");
        require(amount <= amountOwed, "Amount may not exceed amount owed");

        paymentToBeConfirmed = optionalOf(
            TimestampedAmount(amount = amount, timestamp = now())
        );
        notify RepaymentOccurrence(amount, amountOwed);

        become payment_confirmation_required;
    };

    @api
    permission[payee] confirmPayment() | payment_confirmation_required {

        payments = payments.with(paymentToBeConfirmed.getOrFail());
        paymentToBeConfirmed = optionalOf<TimestampedAmount>();
        amountOwed = getAmountOwed();

        if (amountOwed == 0) {
            become repaid;
        } else {
            become unpaid;
        };
    };

    @api
    permission[payee] forgive() | unpaid {
        become forgiven;
    };

    @api
    permission[issuer|payee] getAmountOwedPermission() returns Number {
        return getAmountOwed();
    };
}

Find the IOU example in the npl-integrations repo

Voting App

A voting app between a chairman and a board of members. Each vote is visible only to the voting party, yet can be counted by the system.

This example illustrates how protocol references can be used to create more complex use-cases. Despite parties sharing the same name across different protocols, claims attached to the parties are unique to the protocol instance, allowing for fine-grained access control.

This example leverages the power of parties and impersonation to implement the counting of votes without revealing the votes to the chairman.

board.npl

package board

protocol[chairman, board_member, secretary] Board(
    var board_size: Number
) {

    var ballots: List<Ballot> = listOf<Ballot>();
    var suggested_voting_topics: Set<Text> = setOf<Text>();

    permission[secretary] suggestTopic(topic: Text) {
        this.suggested_voting_topics = this.suggested_voting_topics.with(topic);
    };

    permission[chairman] selectedTopicForVote(topic: Text) returns Ballot {
        require(this.suggested_voting_topics.contains(topic), "Selected topic should be in list of suggested topics.");

        this.suggested_voting_topics = this.suggested_voting_topics.without(topic);

        var ballot: Ballot = Ballot[chairman, board_member](topic, this.board_size);

        this.ballots = this.ballots.with(ballot);

        return ballot;
    };
};

ballot.npl

package board

protocol[chairman, board_member] Ballot(
    var topic: Text,
    var board_size: Number
) {
    require(board_size > 0, "Board size should be bigger than zero");

    initial state VOTING;
    state COUNTING;
    final state COUNTED;

    var votes: List<Vote> = listOf<Vote>();
    var quorum: Number = 0.60; // 60% of the board size must vote to end the vote

    // Yay and Nay votes are counted in the COUNTING state
    var yay_count: Optional<Number> = optionalOf<Number>();
    var nay_count: Optional<Number> = optionalOf<Number>();

    var accepted: Optional<Boolean> = optionalOf<Boolean>();

    permission[*voter] vote(board_member_vote: Vote) | VOTING {
        require(
            voter.mayRepresent(board_member),
            "Only board members can vote");
        require(
            this.votes.noneMatch(function(vote_i: Vote) returns Boolean -> vote_i.isVotingParty[voter]()),
            "Board member cannot vote twice");

        require(
            this.votes.filter(function(vote_i: Vote) returns Boolean -> vote_i.isVotingParty[voter]()).isEmpty(),
            "Keeps only votes that are form the voting party and making sure the list is empty");

        require(
            board_member_vote.isVotingParty[voter](),
            "Voter should be the person that submits the votes");

        this.votes = this.votes.with(board_member_vote);

        if (votes.size() == this.board_size) { // everyone has voted
            become COUNTING;
        };
    };

    permission[*random] endVote() | VOTING {
        require(random.mayRepresent(chairman), "Only Chairman can end vote");
        require(this.votes.size() > quorum * board_size, "Cannot end vote before the quorum is reach");

        become COUNTING;
    };

    permission[chairman] triggerCounting() | COUNTING {
        this.yay_count = optionalOf(
            this.votes.filter(
                function(vote_i: Vote) -> vote_i.member_decision == VotePossibilities.YAY
            ).size());
        this.nay_count = optionalOf(
            this.votes.filter(
                function(vote_i: Vote) -> vote_i.member_decision == VotePossibilities.NAY
            ).size());

        if (this.yay_count.isPresent() && this.nay_count.isPresent()) {
            this.accepted = optionalOf(this.yay_count.getOrFail() > this.nay_count.getOrFail());
        };

        become COUNTED;
    };

    permission[chairman | board_member] changeNothing() {
        this.topic = this.topic;
        this.topic = this.topic;
    };
};

vote.npl

package board

enum VotePossibilities {
    YAY, NAY, BLANK
};

protocol[voter] Vote(
    var member_decision: VotePossibilities
) {
    permission[*anyone] isVotingParty() returns Boolean {
        return getName(voter) == getName(anyone);
    };

    function getName(party: Party) returns Text -> {
        return party.access().getOrNone("name").getOrFail().toList().firstOrNone().getOrFail();
    };
};