Logo
    Logo

    Search

    R3-Solana連携

    Blockchainトレンド

    Corda活用事例

    Corda技術

    おすすめ記事

    記事を探す

    その他

    お客様サポート

    SBI R3 Japan HP

    お問い合わせ

    不動産投資ローンに関するサンプルCordappのご紹介

    公開日
    Jun 5, 2025
    カテゴリ
    Corda活用事例を知る
    タグ
    🏋️‍♂️R3Jスタッフブログ🍀Corda4🔰Corda入門🧑‍💻CorDapp開発
    筆者
    李
    image
    icon
    この記事で学べること

    サンプルCordappについてのユースケース、機能、アプリ、デモをご紹介しています。

    icon
    目次
    • はじめに
    • 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を基盤とした「不動産投資ローンの譲渡および毎月の最大返済額を自動算出するバックエンドシステム」についてご紹介いたします。

    ローカル環境において、Cordapp のnode(図中は Notary)を起動します。
    ローカル環境において、Cordapp のnode(図中は Notary)を起動します。

    1.開発背景

    日本で生活する外国人である筆者も、「いつか日本で自分の家を持てたら」という夢を抱いています。そうした想いと、不動産投資と住宅ローン返済という分野への好奇心・関心から、本システムの開発しました、35年という超長期間と将来の不確実性を考えると、突然の病気や失業によってローンを無事に返済できなくなるのではないかと、とても不安です。

    2.想定ユースケース

    本アプリは住宅ローンプロセスにおいて次のニーズにこたえることができます。

    • 返済処理や契約内容変更が煩雑である課題に対し、ブロックチェーンによる記録の一元化および透明性の向上を図りたい。
    • 不動産の又貸しや権利譲渡が困難な現状に対し、スマートコントラクトを用いたスムーズな権利移転を実現したい。
    本アプリは、上記2つの課題を解決することを目的としています。
    本アプリは、上記2つの課題を解決することを目的としています。

    3.機能紹介

    本アプリには、住宅ローンを想定した2つの機能があります。

    ① 顧客の月収情報に基づいて、ローンの毎月の最大返済額を自動的に設定すること。本アプリでは、毎月のローン最大返済額は、月収の3分の1に設定しています。

    ② 不動産又貸し(権利譲渡)機能を実現。 (ここでいう「又貸し」とは、従来の家主に無断で行うものではなく、家主と元の借主の合意のもとで、新たな借主に貸し出し、期間終了後に元の借主が再び利用するという、新たな形態の又貸しを指します。)

    本アプリは、上記2つの機能を実現することを目指しています。
    本アプリは、上記2つの機能を実現することを目指しています。

    4.アプリの拡張性

    本システムは、将来的には以下のような拡張も視野に入れています。

    • 不動産資産やローン契約情報をもとに、金融機関からの資金調達を支援する仕組みの構築
    • 銀行を Corda ネットワークに参加させることで、改ざん不可能な取引履歴に基づく信用審査の効率化
    • スマートコントラクトを活用した柔軟な返済プラン調整および自動化
    • 既存のローン管理・顧客管理システムとの API 連携による業務効率化
    • 保険業界や住宅管理業界など他業種との連携により、より顧客に魅力的なサービス・商品を創出する可能性

    5.サンプルCordappのシステム概要

    本システムの登場人物

    • ユーザー(顧客)
    • 金融機関(銀行)

    デモシナリオの流れ

    1. ユーザーがローン申請を行う際、給与情報も同時に提供します(例:銀行口座への振込履歴など)。
    2. システムは月収に基づき、最大返済額を自動的に設定します(初期値は月収の1/3)。
    3. 銀行とユーザーの双方が、リアルタイムで収入・返済能力を把握でき、審査判断に活用されます。
    4. ユーザーの収入が変動した場合、システムは自動的に最大返済額を調整し、関係者へ通知します。
    本アプリのシステム概要は上記になります。
    本アプリのシステム概要は上記になります。

    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の台帳上で記録され、関係者のみがアクセス可能です。

    本アプリのState構造は上記になります。
    本アプリのState構造は上記になります。

    Contractの設計

    CordaではStateの生成や移転の制約をContractで行います。本アプリでは複数のフローが存在し、それぞれのフローに対して個別のコントラクト制約条件が設けられています。ここでは一例として、Createコマンドに関するコントラクト設計についてご紹介します。

             Contractのコードの様子。

    7.サンプルCorDappの実施結果

    サンプルCordappの登場人物

    • 顧客:Lee、BOB
    • 銀行:SBIBANK、MUFGBANK

    仮設前提条件

    1枚の銀行カードで給与の受け取りとローン申請を同時に行うことで、銀行と顧客の双方が収入の変動を把握することができます。本アプリでは、毎月最大返済額は自動的に月収の1/3に設定しています。

    4人の登場人物を表すnodeを起動した様子。
    4人の登場人物を表すnodeを起動した様子。

    デモシナリオ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
    CreateLoanFlow成功実施の例。
    CreateLoanFlow成功実施の例。

    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はこの取引を承認することはできません。

    Run VaultQueryの実施結果。
    Run VaultQueryの実施結果。

    SBIBANKはLeeの申請を受け取った後、審査の結果、融資を承認できると判断し、ApproveLoanFlowを使用して申請を承認します。この際、IOUState 内の approvedはtrueに更新されます。

    ステップ2. 住宅ローンの承認(ApproveLoanFlow)

    NodeSBIBANK上で実施するApproveLoanFLowコマンドは以下になります。

    flow start net.corda.samples.example.flows.ApproveLoanFlow linearId: d8e7ea9c-4f19-425c-a703-b30a393b0ae7
    ApproveLoanFlow成功実施の例。
    ApproveLoanFlow成功実施の例。

    approvedの値が trueに変更されたことで、その後のRepayLoanFlowや UpdateSalaryFlowなどのコマンドを実行できるようになります。

    Run VaultQueryでapproved:trueを確認できます。
    Run VaultQueryでapproved:trueを確認できます。

    この時点で、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
    RepayLoanFlow成功実施の例。
    RepayLoanFlow成功実施の例。
    Run VaultQueryでloanTotalAmountの変更を確認できます。
    Run VaultQueryでloanTotalAmountの変更を確認できます。

    翌月、Leeは手元の資金に余裕がなく、10万円ではなく8万円しか返済できませんが、本アプリでの返済は可能です。ただし、creditLimitに設定された金額を超える返済はできません。

    creditLimit以下は返済できますが、creditLimit以上は返済不可。
    creditLimit以下は返済できますが、creditLimit以上は返済不可。
    Run VaultQueryでloanTotalAmountの変更を確認できます。
    Run VaultQueryでloanTotalAmountの変更を確認できます。

    その後、Lee はさらに3回、10万円ずつの返済を行い、残りの返済額は2万円となりました。

    Leeは更に3回、10万円ずつの返済を実施する。
    Leeは更に3回、10万円ずつの返済を実施する。

    この状態で nodeSBIBANKにおいてEndLoanFlowコマンドを実行しても、正常に処理されることはありません。

     loanTotalAmount≠0の時にEndLoanFlowは実施不可。
    loanTotalAmount≠0の時にEndLoanFlowは実施不可。
    RepayLoanFlow成功実施の例。
    RepayLoanFlow成功実施の例。

    ステップ4. 住宅ローンの終了(EndLoanFlow)

    この時点で loanTotalAmount が 0 になっているため、nodeSBIBANK上で以下の EndLoanFlow コマンドを実行することで、本ローンを終了させることができます。

    flow start net.corda.samples.example.flows.EndLoanFlow$Initiator linearId: d8e7ea9c-4f19-425c-a703-b30a393b0ae7
     loanTotalAmount=0の時にEndLoanFlowは実施可能。
    loanTotalAmount=0の時にEndLoanFlowは実施可能。

    EndLoanFlow を正常に実行した後、nodeLeeおよび nodeSBIBANKのどちらからも、d8e7ea9c-4f19-425c-a703-b30a393b0ae7 に関連する取引情報は表示されなくなります。

     EndLoanFlow成功実施後の様子。
     EndLoanFlow成功実施後の様子。

    上記のデモを通じて、「ローン申請 → 銀行の承認 → 返済 → 契約終了」という一連の流れを完了しました。

    デモシナリオ2:Leeがローンを申請する例

    ステップ1. 住宅ローンの申請(CreateLoanFlow)

    次に、年収30万円(最大返済額を10万円に設定欲しい)のBOBという顧客が、SBIBANKに対して総額50万円のローンを申請したと仮定します。SBIBANKがこの申請を承認した後、BOBはまず10万円を返済しました。

    nodeBOB上でCreateLoanFlow コマンドを実行する。

    CreateLoanFlow成功実施後の様子。
    CreateLoanFlow成功実施後の様子。

    run VaultQueryコマンドでIOUStateを確認する。

    Run VaultQueryの実施結果。
    Run VaultQueryの実施結果。

    ステップ2. 住宅ローンの承認(ApproveLoanFlow)

    nodeSBIBANK上でApproveLoanFlowコマンドを実行する。

    ApproveLoanFlow成功実施後の様子。
    ApproveLoanFlow成功実施後の様子。

    ステップ3. 住宅ローンの返済(RepayLoanFlow)

    nodeBOB上でRepayLoanFlowコマンドを実行する。

    flow start net.corda.samples.example.flows.RepayLoanFlow$Initiator linearId: 9364d4f3-8219-413f-a6cf-cf45ecff5800, repaymentValue: 10
    RepayLoanFlow成功実施後の様子。
    RepayLoanFlow成功実施後の様子。

    この時点で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 
    UpdateSalaryFlow成功実施後の様子。
    UpdateSalaryFlow成功実施後の様子。

    この時点で run vaultQuery コマンドを実行することで、monthlySalary および creditLimit の値が変更されたことを確認できます。creditLimit は monthlySalary の3分の1に基づいて自動的に算出された値です。

    Run VaultQueryの実施結果。
    Run VaultQueryの実施結果。

    この時点で、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"
    SwitchBorrowerFlow成功実施後の様子。
    SwitchBorrowerFlow成功実施後の様子。

    その後、run vaultQuery コマンドを実行して状態を確認すると、当該取引情報は node BOB と node MUFGBANK 上で表示され、もともとこの取引を確認できていたnode SBIBANK では表示されなくなります。

    Run VaultQueryの実施結果。
    Run VaultQueryの実施結果。

    仮にこの時点で、BOB が何らかの理由でこれ以上ローンの返済を継続できなくなった場合、node MUFGBANK 上で SwitchLenderFlow を実行することで、顧客を BOBから Lee に変更することが可能です。

    ステップ6. 又貸し機能(SwitchLenderFlow)

    nodeMUFGBANK上でSwitchLenderFlowコマンドを実行する。

    SwitchLenderFlow成功実施後の様子。
    SwitchLenderFlow成功実施後の様子。

    その後、run vaultQuery コマンドを実行して状態を確認すると、当該取引情報は node Lee と node MUFGBANK 上で表示され、もともとこの取引を確認できていたnode BOB では表示されなくなります。

    Run VaultQueryの実施結果。
    Run VaultQueryの実施結果。

    これにて、本アプリにおけるすべての Flow のデモンストレーションが完了しました!

    8.取り組みで工夫した点

    • 現実の住宅ローンでは、返済情報が月ごとに変更されるニーズが存在するため、本アプリでは各月の返済情報を更新可能なState構造「IOUState」として設計しました。また、利用者が給与の変動に応じて返済額を柔軟に変更できるようにすることで、現行制度である「繰上げ返済」にも対応できるよう工夫しました。
    • 実際のローン返済では、金利や為替レートなどの変動要素が影響を与える場合もあります。そのため、今後の拡張に備えてStateに対応する項目をあらかじめ用意し、Cordaのオラクル機能を使って外部データを取り込める構成を検討しました。
    • 現在の日本の法律では、「権利譲渡」や「又貸し」の制度がまだ正式に認められていませんが、本アプリでは将来的に利用可能となるインターフェースや機能を先行して実装しました。制度上の制約がある中でも、商用利用を見据えた拡張性の高い構造にするよう意識しました。

    9.補足情報

    最後まで読んでいただいてありがとうございました!

    社内でもこのビジネスの可能性について大いに議論が盛り上がっており、ビジネス面でこのアプリに興味をお持ちいただける企業様を探したいと思っております

    筆者は本アプリに関するコードをすでにGitHubに公開しています。

    Cordaにご興味のある方は、ぜひダウンロードしていただき、ご一緒にご議論いただければ幸いです。

    ZongruLiSBIR3Japan/MyCordaAppHomework202505

    This is Li’s CordApp homework developed in May 2025 - ZongruLiSBIR3Japan/MyCordaAppHomework202505

    github.com

    ZongruLiSBIR3Japan/MyCordaAppHomework202505
    📬
    最後までお読みいただきありがとうございます。当社へのご質問・ご要望がございましたら、📪SBI R3 Japan お問い合わせフォーム📪よりお気軽にお問い合わせください!

    <ご質問・ご要望の例>

    • Corda Portalの記事について質問したい
    • ブロックチェーンを活用した新規事業を相談したい
    • 企業でのブロックチェーン活用方法を教えて欲しい 等々
    📢
    また、厳選されたCordaに関する最新情報をお伝えるするメールマガジンやX、当社主催のイベントコミュニティを運営しております。ぜひご登録ください。
    • Cordaメールマガジンに登録
    • X(旧Twitter)をフォロー
    • 弊社イベントコミュニティ(Connpass)に参加
    ✍️
    Written by 李 宗儒 (Li Zongru)
    image

    SBI R3 Japan エンジニアリング部所属

    アプリケーション開発/PoC支援を担当しています

    ブロックチェーン技術が将来必ず普及すると信じています!

    →筆者の記事一覧

    Logo

    © copyright SBI R3 Japan 2025

    GitHubYouTubeXFacebookLinkedIn
    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{}
    	    }
        }