如何在Corda上寫一個派股息的Dapp
- 前言
- 架构
- 1. CreateStock 发行股票
- Observer 观察员
- 2. MoveStock 转移股票
- 3. AnnounceDividend 派发股息
- 4. GetStockUpdate 股票更新讯息
- 节点默认不会记录不相关的交易
- 5. ClaimDividendRecivable 要求股息分派
- 获得股东的馀额
- Corda小教训:1 代币 = 1 分 (货币中最小的单位)
- 6.PayDividend 支付股息
- 方便的MoveToken内建组合
- 总结
對應的 Github項目
前言
在CSDN上首次写文章。我经常在世界各地为不同的开发者做有关Corda的培训,发觉他们对新功能代码库 TokenSDK很有兴趣,所以决定写一个相关的样本Dapp作为大家的参考。
这是一个记载了一些我创建此示例CorDapp的过程。基本的Corda的概念我会先跳过,但一些关键内容我会解释一下。
如果您还没有运行示例,可以从这个Github项目中下载并运行。
样本有六个重要的Flow,模拟了股票的发行,转移以至派息时的支付。
架构
这个博客介绍了我创建此示例CorDapp的过程。 尽管我不会介绍所有Corda的基本概念,但我将解释一些关键内容。
样品概述
如果您还没有运行示例,请随时从Github项目中下载并运行建议的示例。
样本有六个重要流。 它们模拟股票的发行,转移以及最终分配方式以及股息的支付方式。
1. CreateStock 发行股票
要发行新的股票,首先要暸解EvolvableToken,因为我接下来创建的StockState 将继承这个class,而它的特性是能够独立的更新代币(Token)的特性,而不影响代币的持有人。
而要创造StockState,我们只需使用Token-SDK内建的CreateEvolvableToken Flow。 这个StockState包含股票信息,例如股票名称,股票代码,宣布股息等。您可以扩展它以添加更多信息,例如年度报告等等。
Observer 观察员
在实际发行股票时,应通知证监会等监管机构。 在Corda,我们将这些监管机构视为观察员,他们只是记录State的变化,无需签署交易。 做法就像以下这一行代码,提供的observers参数将只记录stockState的创造。
subFlow(new CreateEvolvableTokens(stockState,observers));
在TokenSDK中,大多数内建Flow都带有一个observer参数,开发人员不必自己特意写一个观察员用的flow,非常方便。
2. MoveStock 转移股票
MoveStock这步很直白简单。在TokenSDK中,把Token从一方转移到另一方就像是一行的代码那麽容易。 我们不仅可以将股票转让给其他人,还可以交换其他一些资产,例如金钱。 我们稍后的篇幅再细说。
subFlow(new MoveFungibleTokens(amount, recipient));
3. AnnounceDividend 派发股息
AnnounceDividend 构建的 Transaction Input : 没有股息的TokenType Output : 发布了股息的TokenType 不受影響 : 股东StockState这一个Flow的编写看起来很简单,但它的概念比较复杂,因为牵动往後支付股息的流程,因而设计上的考虑很多。
我公布股息是更改StockState拥有的特性及日期。这里我们可以善用EvolvableToken的特性,利用内建的UpdateEvolvableTokenFlow独立地更新StockState的特性,而不会触发一个简单的动作向成千上万的股东发送讯息,为网络带来大量流量,但这亦意味着股东不会意识到这个AnnounceDividend flow所宣布的股息,正因如此,我设计了接下来的GetStockUpdate flow。
(当然这个设计有很大讨论的空间,但在这个样本CorDapp中我旨在演示TokenSDK的便利。)
4. GetStockUpdate 股票更新讯息
如果只在分散式赈本的层面上,这个flow感觉会有点画蛇添足。但在应用层面上,跟很多实际案例一样,股东的用户端都会向股份公司或证券商发起更新请求以获得最新的资讯。编写这个flow,我们可以使用Corda内置的flow SendTransactionFlow和ReceiveTransactionFlow,便可在节点之间交换transaction。
subFlow(new SendTransactionFlow(holderSession, stx));
假如在执行AnnounceDividend flow之后,我们跳过这步直接在股东的节点中运行5. ClaimDividendReceivable flow,会出现包含历史状态(historical state)的错误。 这是因为股东没有最新的StockState状态,因而用已消耗的状态建立交易,违反了Corda设计。这避免了有股东节点在没有收到最新消息的状态下要求股息。
节点默认不会记录不相关的交易
同时间,在这个调用ReceiveTransactionFlow你会发现有StatesToRecord.ALL_VISIBLE这一个参数。
SignedTransaction stx = subFlow(new ReceiveTransactionFlow(session, true, StatesToRecord.ALL_VISIBLE));
Corda本身的设计是节点不会记录自己不是参与者(Participant)的交易。所以即使股东接收到AnnounceDividend的交易,也不会记录因为股东没有参与其中。要强迫股东节点把这种不相关的交易,便需要用到这个StatesToRecord.ALL_VISIBLE。
5. ClaimDividendRecivable 要求股息分派
这个flow是模疑除息日所设计的。在这天系统要记录哪些股东符合获得股息,所以我们要做的,是把合资格股东所持有的StockState的资讯记录下
来,而我创建了另一种State类来记录他们,就是DividendState。
整个DividendState的概念是最後在支付日(Payday),我们可以用这DividendState来更换其他货币,甚或至更多的股息。
在这个复杂的flow中,我们也可以从要构建一个怎样交易开始想。这个交易将需要从没有创造一个DividendState出来,这个时候我们利用ReferenceState的功能,把股东现在持有的StockState放作交易中的ReferenceState,让这个交易变得更有依据跟参考性。
ClaimDividendRecivable 构建的 Transaction 参考Reference : 股东StockState Input : DividendState Output : DividendState获得股东的馀额
在股份公司方构建这个transaction,需要股东的馀额,这里我创造了一个class ClaimNotification来承载馀额及有需要的资讯,股东发送有必要的资讯到公司系创建transaction。
这种方法在Corda上是很常见的,可以学起来。
(讨论: 这里我让股东成为发起人来要求股息,而不是公司。 可能公司发起这个flow更近似於现实情况。 但这将导致我们得再创造多一次交流,使此样本过于复杂,所以我省略了。)
Corda小教训:1 代币 = 1 分 (货币中最小的单位)
之后我所需要的就是计算股息并创建一个DividendSState,我以为这很简单。 然而我花了半天才计算出收益率和股利!
我没有正确处理价格的小数位数。 以美元为例,一个代币等于一美分,而不是一美元。 这是Corda Token概念的基础。
要计算股票的价值,我们需要将股票价格,乘以货币的小数位(fractional digit)作为10的幂。
BigDecimal stockValue = stockPrice.multiply(BigDecimal.valueOf(Math.pow(10.0, currency.getDefaultFractionDigits())))
6.PayDividend 支付股息
PayDividend 构建的 Transaction Input : DividendState Input : 股东现金 Output : 股东现金 Input : 公司现金 Output : 公司现金除息日後的一段时间,便是当支付日(Payday)。这里我们需要一个flow将这个state转换为法定货币(即现金),这就是这个PayDividend目的。
在这个flow中我们要构建的交易中,需要之前创建DividendState作为Input,同时因为要转换现金,所以需要股东及股份公司原本及支付股息後的货币馀额作为input及output。
要把这股东及股份的馀额逐一从vault取出再逐一放进Transaction中,我们还可以使用TokenSDK的内建方法帮助。
方便的MoveToken内建组合
TokenSelection.generateMove()
上面的这行代码,便可生成一对Token移动前跟移动後的State物件。 这背後的原理跟鼓励大家使用的原因,是TokenSelection能用一个高效算法来查询所有有关的Token,开发者省去了要查询全部Token的state再计算总和的工夫。
(为了方便,在这里我暂时创建了一个TempTokenSelectionFactory来创建TokenSelection,现在它在Java constructor跟还需要优化。)
然後重我们可以使用以下这个代码把这对State直接放入Transaction中。
MoveTokensUtilitiesKt.addMoveTokens()
通过使用以上这个组合TokenSelection.generateMove()和MoveTokensUtilitiesKt.addMoveTokens(),我们只需要几行代码便能转移Token,在一些需要特别订制的flow,开发者也有合适的工具去轻易应付处理Token的繁琐程序。
总结
创建此Cordapp非常有挑战性,因为我没有太多的财务知识。 但是我可以感觉到TokenSDK如何把交易不同的数位财产的代码编写变得轻松。
感谢阅读完本文,希望您喜欢。