A benefits application where organizations (e.g. companies) can contribute funds regularly (always on the first day of a month) to the benefits accounts of individuals (e.g. employees).
Individuals should have detailed and aggregate insights into their benefits. Funds in a benefit account can be spent with suitable service providers.
Using the GRANDstack, we can effortlessly translate a GraphQL schema into the actual data layer with Neo4j and generate queries and mutations through augmented schema-generation on Apollo-Server.
The nodes and relationships in black represent simple core data that can and should be created through the GraphQL API and its created mutations. For greater flexibility and avoidance of UUID clashes, these nodes share a common index on the id
property through implementation of the Queryable
interface.
The red nodes and relationships are created through custom mutations that interact with the Plutus Application Backend. Upon successful (cron-scheduled) execution, this data represents metadata that should not be modified directly; augmented schema-generation for mutations for these nodes and relationships need to be disabled. The same applies for the blue path, which represents the generated data upon successful redemption of an Offer
by a Beneficiary
.
Nonetheless, the unspent UTXO nodes carry the balance of Beneficiary
, Contributor
, and Provider
wallets. These nodes implement a resolver that aggregates balances.
Useful reading: Primer on (e)UTXO
interface Queryable {
id: ID! @id
}
enum FundingFrequency {
MONTHLY
QUARTERLY
ANNUALLY
}
type Beneficiary implements Queryable {
id: ID! @id
name: String!
email: String
wallet: [Wallet] @relationship(type: "OWNS", direction: OUT)
}
"""The contributing entity (e.g. a company)."""
type Contributor implements Queryable {
id: ID! @id
name: String!
"""This are the _Benefit Accounts_ mentioned in the business requirements."""
contributions: [Contribution] @relationship(type: "MAKES", direction: OUT)
wallets: [Wallet] @relationship(type: "OWNS", direction: OUT)
}
"""The deposit data that is used for scheduling transactions."""
type Contribution implements Queryable {
id: ID! @id
name: String!
"""The funding amount in cent."""
amount: Int!
usedWallet: Wallet! @relationship(type: "USING", direction: OUT)
"""The wallets of beneficiaries to send `amount` funds to periodically."""
fundsWallets: [Wallet] @relationship(type: "FUNDS", direction: OUT)
offers: [Offer] @relationship(type: "COVERS", direction: OUT)
fundingFrequency: FundingFrequency!
createdAt: DateTime! @timestamp(operations: [CREATE])
updatedAt: DateTime @timestamp(operations: [UPDATE])
}
type Offer implements Queryable {
id: ID! @id
name: String!
"""The monthly cost of being able to use this offer in cent."""
cost: Int!
provider: Provider! @relationship(type: "OFFERS", direction: IN)
wallet: Wallet! @relationship(type: "USING", direction: OUT)
}
type Provider implements Queryable {
id: ID! @id
name: String!
offers: [Offer] @relationship(type: "OFFERS", direction: OUT)
wallets: [Wallet] @relationship(type: "OWNS", direction: OUT)
}
union WalletOwner = Beneficiary | Contributor | Provider
type Wallet {
pkh: ID! @id
owner: WalletOwner @relationship(type: "OWNS", direction: IN)
utxos: [UTXO] @relationship(type: "WITH", direction: OUT)
createdTransactions: [Transaction] @relationship(type: "CREATED", direction: OUT)
balances: [Balance] @cypher(statement:"""
MATCH (this)<-[:FUNDS]-(c:Contribution)<-[:FOR]-(u:UTXO)<-[:WITH]-(this)
WHERE NOT EXISTS((u)-[:IS_INPUT_OF]->())
RETURN {
contribution: c,
spendable: sum(u.amount)
}
""")
}
"""Pseudo-type to pair a wallet's contributions and their aggregate spending power."""
type Balance {
contribution: Contribution
spendable: Int
}
type UTXO @exclude(operations: [CREATE, UPDATE, DELETE]) {
utxoid: ID! @id
createdAt: DateTime! @timestamp(operations: [CREATE]) @readonly
"""The balance of this UTXO in cent."""
amount: Int! @readonly
"""If true, this UTXO has been spent and cannot be applied as input for another transaction."""
isSpent: Boolean! @cypher(statement:"""RETURN EXISTS((this)-[:IS_INPUT_OF]->())""")
wallet: Wallet! @relationship(type: "WITH", direction: IN) @readonly
"""The `Contribution` category this UTXO can be spent on. If null, `amount` can be spent on anything (incl. withdrawal)."""
contribution: Contribution @relationship(type: "FOR", direction: OUT) @readonly
createdBy: Transaction @relationship(type: "CREATED", direction: IN) @readonly
cocreated: Transaction @relationship(type: "IS_INPUT_OF", direction: OUT)
}
type Transaction @exclude(operations: [CREATE, UPDATE, DELETE]) {
txid: ID! @id
createdAt: DateTime! @timestamp(operations: [CREATE])
inputs: [UTXO] @relationship(type: "IS_INPUT_OF", direction: IN) @readonly
outputs: [UTXO] @relationship(type: "CREATED", direction: OUT) @readonly
contribution: [Contribution] @relationship(type: "FOR", direction: OUT) @readonly
}
Provided through the schema. However, several transactional nodes and relationships must not be created in isolation via augmented mutations (e.g. Transaction, UTXO and their relationships).
The PAB facilitates the actual payment from Contributor
to Beneficiary
and from Beneficiary
to Provider
.
The Mediation Contract serves as an example how a similar contract can be built for this benefits application.
The cron job queries the Contribution
nodes and its relevant connections to create the payloads to be sent to the PAB.
These requests will be sent to a queue and be purposefully held for review for a configurable duration. There, funding requests can be manually edited, cancelled, or approved. Untouched funding requests during this review period will be automatically sent out to the PAB - which results in the actual payment within seconds.
- e2e
- resilience testing
- cypress
- unit tests
- load tests
- Property based testing using quick check
- Mock chain testing using PAB and shell scripts (Example of testing a smart contract flow using curl)
- Integration test on testnet
Term | Description |
---|---|
Beneficiary | An entity (commonly a natural person, such as an employee) that may own one or multiple accounts. |
Contribution | A scheduled deposit to one or many wallets held by beneficiaries that recurs on a cycle (monthly, quarterly, or annually). |
Contributor | An entity (e.g. company) that contributes funds to Wallet nodes (via the smart contract API which yields resulting Transaction and UTXO nodes). |
Offer | A service (e.g. yoga class) offered by a Provider . |
Provider | An entity (e.g. yoga studio) that provides a service (Offer ) that can be utilized by a Beneficiary using the funds in their Wallet . |
Wallet | A loose synonym is Account. (In reality, this is closer to being a collection of payment addresses that is associated with one entity that controls a private key.) |