Chromia
Start Building

04 / 06Intermediate

Subscription chain

The next step is the Subscription Chain. It needs two entities: account, which holds the account balance, and receipt, which records digital receipts for logging. Start by configuring a new blockchain in chromia.yml.

yaml
blockchains:  # ↓↓↓ Delete this code snippet ↓↓↓  digital_warehouse_example:    module: main  # ↑↑↑ Delete this code snippet ↑↑↑  # ↓↓↓ Add this code snippet ↓↓↓  subscription_chain:    module: subscription_chain# ↑↑↑ Add this code snippet ↑↑↑

Then put the following code in entities.rell inside the src/subscription_chain directory:

src/subscription_chain/entities.rell
entity account {    key id: pubkey;    mutable balance: integer = 10000;}@logentity receipt {    // We don't reference the account entity here since this entity may be removed    account_id: pubkey;    amount: integer;    period;    blockchain_rid: byte_array;    index account_id, blockchain_rid;}

An account is an entity with an ID and a mutable balance. To keep the example simple, every new account starts with 10,000 tokens.

The receipt entity carries the @log annotation, so its entries can never be changed once written. This immutability matters when you need a transparent record of monetary transactions. Each receipt points to its account through account_id rather than a direct reference to the account entity. That way, you can still delete an account if legal reasons require it.

A receipt stores the amount paid, the period purchased, and the blockchain RID (Resource IDentifier) of the Digital Warehouse Chain that the payment was meant for. With this information, the Warehouse Chain can confirm the payment and guard against the same payment being spent on more than one chain. An index on account_id and blockchain_rid speeds up queries.

Account creation

In this example, an account is created on the fly the first time a user subscribes. Add the following function, which handles that, to functions.rell in the src/subscription_chain folder.

src/subscription_chain/functions.rell
function get_or_create_account(id: pubkey) {    require(op_context.is_signer(id));    return account @? { id } ?: create account(id);}

Subscription

Subscribing is handled by an operation named subscribe. It takes the blockchain RID of the chain where the transaction will be confirmed, along with the subscription metadata.

src/subscription_chain/operations.rell
operation subscribe(blockchain_rid: byte_array, subscription) {    val account = get_or_create_account(subscription.account_id);    val subscription_fee = period_price(subscription.period);    require(account.balance >= subscription_fee, "Insufficient funds");    account.balance-=subscription_fee;    create receipt (        account_id = subscription.account_id,        subscription_fee,        subscription.period,        blockchain_rid    );}

The operation fetches the user's account or creates one if none exists, subtracts the cost from the balance, and writes a receipt.

Queries

For monitoring, you will want to look up receipts by user or by transaction. To finish the dapp, add these queries to queries.rell in the src/subscription_chain directory.

src/subscription_chain/queries.rell
query get_receipts(account_id: pubkey?, blockchain_rid: byte_array?, from: timestamp?)    = (receipt, account) @* {    if (account_id??) account.id == account_id else true,    account.id == receipt.account_id,    if (blockchain_rid??) .blockchain_rid == blockchain_rid else true,    if (from??) .transaction.block.timestamp >= from else true} (    receipt_id = .transaction.tx_rid,    account_id = account.id,    period = .period,    payment_amount = .amount,    blockchain_rid = .blockchain_rid,    timestamp = .transaction.block.timestamp);

You can use this query to filter receipts by account_id, blockchain_rid, and timestamp.

Unit test

Testing this blockchain takes little effort. Create a file called subscription_chain_test.rell in the src/test directory and paste in this code:

src/test/subscription_chain_test.rell
@test module;import subscription_chain.{ subscribe, subscription, period, account, period_price, period, get_receipts };val TEST_WAREHOUSE_CHAIN = x"ABAB";function test_create_subscription() {    rell.test.tx()        .op(subscribe(TEST_WAREHOUSE_CHAIN, subscription(rell.test.pubkeys.alice, period.WEEK)))        .sign(rell.test.keypairs.alice)        .run();    val test_account = account @? { rell.test.pubkeys.alice };    assert_not_null(test_account);    assert_equals(test_account.balance, 10000 - period_price(period.WEEK));    val receipts = get_receipts(test_account.id, null, null);    assert_equals(receipts.size(), 1);    assert_equals(receipts[0].payment_amount, 30);    assert_equals(receipts[0].blockchain_rid, TEST_WAREHOUSE_CHAIN);}

You also need to extend chromia.yml with the following configuration:

chromia.yml
blockchains:  subscription_chain:    module: subscription_chain    # ↓↓↓ Add this code snippet ↓↓↓    test:      modules:        - test.subscription_chain_test    # ↑↑↑ Add this code snippet ↑↑↑# ↓↓↓ Delete this code snippet ↓↓↓test:  modules:    - test# ↑↑↑ Delete this code snippet ↑↑↑

The test subscribes the test user Alice and verifies that the resulting receipt has the expected amounts and target blockchain.

Run the test with:

text
chr test

For reference, here is the project structure and the full chromia.yml file as they should look after these changes. Use them to check your work at this point in the course:

Project structure

text
digital_warehouse_example/├── build/├── src/│   ├── lib/│   ├── subscription_chain/│   │   ├── entities.rell│   │   ├── functions.rell│   │   ├── module.rell│   │   ├── operations.rell│   │   └── queries.rell│   ├── test/│   │   └── subscription_chain_test.rell│   └── main.rell├── .gitignore└── chromia.yml

Final version of the chromia.yml file

text
yaml title="chromia.yml"blockchains:  subscription_chain:    module: subscription_chain    test:      modules:        - test.subscription_chain_test  digital_warehouse_chain:    module: digital_warehouse_chain    config:      gtx:        modules:          - net.postchain.d1.iccf.IccfGTXModule    moduleArgs:      digital_warehouse_chain:        warehouse_id: 1    test:      modules:        - test.digital_warehouse_chain_testcompile:  rellVersion: 0.14.9database:  schema: schema_digital_warehouse_examplelibs:  iccf:    registry: https://gitlab.com/chromaway/core/directory-chain    path: src/lib/iccf    tagOrBranch: 1.87.0    rid: x"9C359787B75927733034EA1CEE74EEC8829D2907E4FC94790B5E9ABE4396575D"  iccf_test:    registry: https://gitlab.com/chromaway/core/directory-chain    path: src/lib/test/iccf    tagOrBranch: 1.87.0    rid: x"873D71557531E35E453587FA71C2D3CCB674F0EB49D18B30FA838C52C1155EB3"

Written for this site from Chromia’s public course. Code samples are the course’s own.