サンプルCordappについてのユースケース、機能、アプリ、デモをご紹介しています。
- はじめに
- 1.開発背景
- 2.想定ユースケース
- 3.機能紹介
- 4.アプリの拡張性
- 5.サンプルCordappのシステム概要
- 本システムの登場人物
- デモシナリオの流れ
- 6.サンプルCordappの技術紹介
- Flowの設計
- Stateの設計
- Contractの設計
- 7.サンプルCorDappの実施結果
- サンプルCordappの登場人物
- 仮設前提条件
- デモシナリオ1:Leeがローンを申請する例
- ステップ1. 住宅ローンの申請(CreateLoanFlow)
- ステップ2. 住宅ローンの承認(ApproveLoanFlow)
- ステップ3. 住宅ローンの返済(RepayLoanFlow)
- ステップ4. 住宅ローンの終了(EndLoanFlow)
- デモシナリオ2:Leeがローンを申請する例
- ステップ1. 住宅ローンの申請(CreateLoanFlow)
- ステップ2. 住宅ローンの承認(ApproveLoanFlow)
- ステップ3. 住宅ローンの返済(RepayLoanFlow)
- ステップ4. 住宅ローンの月収調整(UpdateSalaryFlow)
- ステップ5. 住宅ローンの銀行変更(SwitchBorrowerFlow)
- ステップ6. 又貸し機能(SwitchLenderFlow)
- 8.取り組みで工夫した点
- 9.補足情報
はじめに
筆者は2025年4月にSBI R3 Japan株式会社へ中途入社したエンジニアです。それ以前はIT業界にて3年間、ブリッジSEとしての業務経験およびC#・Javaを用いたバックエンドのバッチ開発に従事してきました。
本記事は、SBI R3 Japan株式会社の中途採用エンジニアのオンボーディングにおける研修成果発表の一環として発表したものであり、個人的に関心を寄せている不動産投資分野を題材に、Corda 4.12を基盤とした「不動産投資ローンの譲渡および毎月の最大返済額を自動算出するバックエンドシステム」についてご紹介いたします。
1.開発背景
日本で生活する外国人である筆者も、「いつか日本で自分の家を持てたら」という夢を抱いています。そうした想いと、不動産投資と住宅ローン返済という分野への好奇心・関心から、本システムの開発しました、35年という超長期間と将来の不確実性を考えると、突然の病気や失業によってローンを無事に返済できなくなるのではないかと、とても不安です。
2.想定ユースケース
本アプリは住宅ローンプロセスにおいて次のニーズにこたえることができます。
- 返済処理や契約内容変更が煩雑である課題に対し、ブロックチェーンによる記録の一元化および透明性の向上を図りたい。
- 不動産の又貸しや権利譲渡が困難な現状に対し、スマートコントラクトを用いたスムーズな権利移転を実現したい。
3.機能紹介
本アプリには、住宅ローンを想定した2つの機能があります。
① 顧客の月収情報に基づいて、ローンの毎月の最大返済額を自動的に設定すること。本アプリでは、毎月のローン最大返済額は、月収の3分の1に設定しています。
② 不動産又貸し(権利譲渡)機能を実現。 (ここでいう「又貸し」とは、従来の家主に無断で行うものではなく、家主と元の借主の合意のもとで、新たな借主に貸し出し、期間終了後に元の借主が再び利用するという、新たな形態の又貸しを指します。)
4.アプリの拡張性
本システムは、将来的には以下のような拡張も視野に入れています。
- 不動産資産やローン契約情報をもとに、金融機関からの資金調達を支援する仕組みの構築
- 銀行を Corda ネットワークに参加させることで、改ざん不可能な取引履歴に基づく信用審査の効率化
- スマートコントラクトを活用した柔軟な返済プラン調整および自動化
- 既存のローン管理・顧客管理システムとの API 連携による業務効率化
- 保険業界や住宅管理業界など他業種との連携により、より顧客に魅力的なサービス・商品を創出する可能性
5.サンプルCordappのシステム概要
本システムの登場人物
- ユーザー(顧客)
- 金融機関(銀行)
デモシナリオの流れ
- ユーザーがローン申請を行う際、給与情報も同時に提供します(例:銀行口座への振込履歴など)。
- システムは月収に基づき、最大返済額を自動的に設定します(初期値は月収の1/3)。
- 銀行とユーザーの双方が、リアルタイムで収入・返済能力を把握でき、審査判断に活用されます。
- ユーザーの収入が変動した場合、システムは自動的に最大返済額を調整し、関係者へ通知します。
6.サンプルCordappの技術紹介
Flowの設計
本アプリでは7つのFlowが設計されています。
・CreateLoanFLow:ローンを新規申請
・ApproveLoanFLow:銀行側による新規申請したローンを承認、承認後下のFlowを実施可能
・RepayLoanFlow:顧客側による月次ローンを返済
・UpdateSalaryFLow:銀行側による顧客の月収情報を調整
・SwitchLenderFlow:顧客が銀行変更
・SwitchBorrowerFlow:又貸し(権利譲渡)
・EndLoanFlow:残りの返済総額は「0」になった場合、銀行側による完済操作
Stateの設計
本アプリのStateとパラメータは以下になります。
- value:毎月の返済額
- lender / borrower:TX当事者
- monthlySalary:お客様の月収
- loanTotalAmount:残りのローン総額
- creditLimit:返済額の上限
- approved:銀行の承認フラグ
- linearId:ローンごとの識別子
この情報はCordaの台帳上で記録され、関係者のみがアクセス可能です。
Contractの設計
CordaではStateの生成や移転の制約をContractで行います。本アプリでは複数のフローが存在し、それぞれのフローに対して個別のコントラクト制約条件が設けられています。ここでは一例として、Createコマンドに関するコントラクト設計についてご紹介します。
public class IOUContract implements Contract {
public static final String ID = "net.corda.samples.example.contracts.IOUContract";
/**
* The "verify()" function of all the states' contracts must not throw an exception for a transaction to be
* considered valid.
*/
@Override
public void verify(LedgerTransaction tx) {
final CommandWithParties<CommandData> command = requireSingleCommand(tx.getCommands(), CommandData.class);
if (command.getValue() instanceof Commands.Create) {
// create a loan, with 0 input and 1 output
requireThat(require -> {
// Generic constraints around the IOU transaction.
final IOUState out = tx.outputsOfType(IOUState.class).get(0);
require.using("The lender and the borrower cannot be the same entity.",
!out.getLender().equals(out.getBorrower()));
require.using("All of the participants must be signers.",
new HashSet<>(command.getSigners()).containsAll(out.getParticipants().stream().map(AbstractParty::getOwningKey).toList()));
// IOU-specific constraints.
require.using("No payment should be set during application of loan!",
out.getValue() == 0);
require.using("Initial loan total amount must be greater than 0.",
out.getLoanTotalAmount() > 0);
int salary = out.getMonthlySalary();
require.using("You can not apply loan from Bank since you don't have salary.",
salary >0);
int initial_paybackvalue = out.getValue();
require.using("You can not set initial monthly pay back more than 1/3 of your salary!",3 * initial_paybackvalue - salary <= 0);
return null;
});
} else if (command.getValue() instanceof Commands.Repay) {...}
else if (command.getValue() instanceof Commands.EndLoan) {...}
else if (command.getValue() instanceof Commands.SwitchLender) {...}
else if (command.getValue() instanceof Commands.SwitchBorrower) {...}
else if (command.getValue() instanceof Commands.Approve) {...}
public interface Commands extends CommandData {
class Create implements Commands {}
class Repay implements Commands {}
class UpdateSalary implements Commands{}
class SwitchLender implements Commands{}
class SwitchBorrower implements Commands{}
class Approve implements Commands{}
class EndLoan implements Commands{}
}
}
Contractのコードの様子。
7.サンプルCorDappの実施結果
サンプルCordappの登場人物
- 顧客:Lee、BOB
- 銀行:SBIBANK、MUFGBANK
仮設前提条件
1枚の銀行カードで給与の受け取りとローン申請を同時に行うことで、銀行と顧客の双方が収入の変動を把握することができます。本アプリでは、毎月最大返済額は自動的に月収の1/3に設定しています。
デモシナリオ1:Leeがローンを申請する例
ステップ1. 住宅ローンの申請(CreateLoanFlow)
ある日、Lee
という人が、月収30万円(最大返済額を10万円に設定欲しい)をもとに、SBIBANK
に対して50万円の住宅ローンを申請しました。
nodeLee
上で以下のシェルコマンドを実行することで、上記のプロセスをシミュレーションします。
flow start net.corda.samples.example.flows.CreateLoanFlow$Initiator iouValue: 0, otherParty: "O=SBIBANK,L=New York,C=US", monthly_salary: 30, loan_total_amount: 50, credit_limit: 10
run VaultQuery
コマンドを実行することで、各nodeが保持する State の情報を取得できます。今回の取引は nodeLee
とnodeSBIBANK
の間で行われたため、それ以外のnodeからは当該取引情報を閲覧することはできません。
run VaultQuery
コマンドは以下になります。
run vaultQuery contractStateType: net.corda.samples.example.states.IOUState
今後のフロー操作を行うために、linearId
内の ID
を取得する必要があります。
CreateLoanFlow
実行後、IOUState の approved
は初期状態で false に設定され、銀行の承認を得ていない限り RepayLoanFlow
や UpdateSalaryFlow
などの操作は行えません。
また、この取引に関与した銀行のみが ID
を使って ApprovedLoanFlow
を実行し、承認を行うことができます。つまり、MUFGBANK
はこの取引を承認することはできません。
SBIBANK
はLee
の申請を受け取った後、審査の結果、融資を承認できると判断し、ApproveLoanFlow
を使用して申請を承認します。この際、IOUState
内の approved
はtrue
に更新されます。
ステップ2. 住宅ローンの承認(ApproveLoanFlow)
NodeSBIBANK
上で実施するApproveLoanFLow
コマンドは以下になります。
flow start net.corda.samples.example.flows.ApproveLoanFlow linearId: d8e7ea9c-4f19-425c-a703-b30a393b0ae7
approved
の値が true
に変更されたことで、その後のRepayLoanFlow
や UpdateSalaryFlow
などのコマンドを実行できるようになります。
この時点で、Lee
も approved:true
の情報を確認し、それを受けて RepayLoanFlow
を実行し、返済を開始しました。初月は、自身の最大限度額である10万円を返済します。
ステップ3. 住宅ローンの返済(RepayLoanFlow)
NodeLee
上で実施するRepayLoanFLow
コマンドは以下になります。
flow start net.corda.samples.example.flows.RepayLoanFlow$Initiator linearId: d8e7ea9c-4f19-425c-a703-b30a393b0ae7, repaymentValue: 10
翌月、Lee
は手元の資金に余裕がなく、10万円ではなく8万円しか返済できませんが、本アプリでの返済は可能です。ただし、creditLimit
に設定された金額を超える返済はできません。
その後、Lee
はさらに3回、10万円ずつの返済を行い、残りの返済額は2万円となりました。
この状態で nodeSBIBANK
においてEndLoanFlow
コマンドを実行しても、正常に処理されることはありません。
ステップ4. 住宅ローンの終了(EndLoanFlow)
この時点で loanTotalAmount
が 0 になっているため、nodeSBIBANK
上で以下の EndLoanFlow
コマンドを実行することで、本ローンを終了させることができます。
flow start net.corda.samples.example.flows.EndLoanFlow$Initiator linearId: d8e7ea9c-4f19-425c-a703-b30a393b0ae7
EndLoanFlow
を正常に実行した後、nodeLee
および nodeSBIBANK
のどちらからも、d8e7ea9c-4f19-425c-a703-b30a393b0ae7
に関連する取引情報は表示されなくなります。
上記のデモを通じて、「ローン申請 → 銀行の承認 → 返済 → 契約終了」という一連の流れを完了しました。
デモシナリオ2:Leeがローンを申請する例
ステップ1. 住宅ローンの申請(CreateLoanFlow)
次に、年収30万円(最大返済額を10万円に設定欲しい)のBOB
という顧客が、SBIBANK
に対して総額50万円のローンを申請したと仮定します。SBIBANKがこの申請を承認した後、BOBはまず10万円を返済しました。
nodeBOB
上でCreateLoanFlow
コマンドを実行する。
run VaultQuery
コマンドでIOUState
を確認する。
ステップ2. 住宅ローンの承認(ApproveLoanFlow)
nodeSBIBANK
上でApproveLoanFlow
コマンドを実行する。
ステップ3. 住宅ローンの返済(RepayLoanFlow)
nodeBOB
上でRepayLoanFlow
コマンドを実行する。
flow start net.corda.samples.example.flows.RepayLoanFlow$Initiator linearId: 9364d4f3-8219-413f-a6cf-cf45ecff5800, repaymentValue: 10
この時点でBOB
の給与が30万円から90万円に急増したと仮定します。SBIBANK
もこの情報を同時に取得できるため、nodeSBIBANK
上でUpdateSalaryFlow
を実行することで、BOB
の収入情報を更新し、それに基づいて自動的に最大返済額が再計算されます。
ステップ4. 住宅ローンの月収調整(UpdateSalaryFlow)
nodeSBIBANK
上でUpdateSalaryFlow
コマンドを実行する。
flow start net.corda.samples.example.flows.UpdateSalaryFlow$Initiator linearId: 9364d4f3-8219-413f-a6cf-cf45ecff5800, newMonthlySalary: 90
この時点で run vaultQuery
コマンドを実行することで、monthlySalary
および creditLimit
の値が変更されたことを確認できます。creditLimit
は monthlySalary
の3分の1に基づいて自動的に算出された値です。
この時点で、BOB
が突然銀行をSBIBANK
からMUFGBANK
に変更したいと考えた場合、nodeBOB
上で SwitchBorrowerFlow
を実行することで、この変更を実現することができます。
ステップ5. 住宅ローンの銀行変更(SwitchBorrowerFlow)
nodeBOB
上でSwitchBorrowererFlow
コマンドを実行する。
flow start net.corda.samples.example.flows.SwitchBorrowerFlow$Initiator linearId: 9364d4f3-8219-413f-a6cf-cf45ecff5800, newBorrower: "O=MUFGBANK,L=Osaka,C=JP"
その後、run vaultQuery
コマンドを実行して状態を確認すると、当該取引情報は node BOB
と node MUFGBANK
上で表示され、もともとこの取引を確認できていたnode SBIBANK
では表示されなくなります。
仮にこの時点で、BOB
が何らかの理由でこれ以上ローンの返済を継続できなくなった場合、node MUFGBANK
上で SwitchLenderFlow
を実行することで、顧客を BOB
から Lee
に変更することが可能です。
ステップ6. 又貸し機能(SwitchLenderFlow)
nodeMUFGBANK
上でSwitchLenderFlow
コマンドを実行する。
その後、run vaultQuery
コマンドを実行して状態を確認すると、当該取引情報は node Lee
と node MUFGBANK
上で表示され、もともとこの取引を確認できていたnode BOB
では表示されなくなります。
これにて、本アプリにおけるすべての Flow のデモンストレーションが完了しました!
8.取り組みで工夫した点
- 現実の住宅ローンでは、返済情報が月ごとに変更されるニーズが存在するため、本アプリでは各月の返済情報を更新可能なState構造「IOUState」として設計しました。また、利用者が給与の変動に応じて返済額を柔軟に変更できるようにすることで、現行制度である「繰上げ返済」にも対応できるよう工夫しました。
- 実際のローン返済では、金利や為替レートなどの変動要素が影響を与える場合もあります。そのため、今後の拡張に備えてStateに対応する項目をあらかじめ用意し、Cordaのオラクル機能を使って外部データを取り込める構成を検討しました。
- 現在の日本の法律では、「権利譲渡」や「又貸し」の制度がまだ正式に認められていませんが、本アプリでは将来的に利用可能となるインターフェースや機能を先行して実装しました。制度上の制約がある中でも、商用利用を見据えた拡張性の高い構造にするよう意識しました。
9.補足情報
最後まで読んでいただいてありがとうございました!
社内でもこのビジネスの可能性について大いに議論が盛り上がっており、ビジネス面でこのアプリに興味をお持ちいただける企業様を探したいと思っております
筆者は本アプリに関するコードをすでにGitHubに公開しています。
Cordaにご興味のある方は、ぜひダウンロードしていただき、ご一緒にご議論いただければ幸いです。
<ご質問・ご要望の例>
- Corda Portalの記事について質問したい
- ブロックチェーンを活用した新規事業を相談したい
- 企業でのブロックチェーン活用方法を教えて欲しい 等々
SBI R3 Japan エンジニアリング部所属
アプリケーション開発/PoC支援を担当しています
ブロックチェーン技術が将来必ず普及すると信じています!