├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ChaincodeOverview.md ├── LICENSE ├── README.md ├── api ├── .gitignore ├── app.js ├── config.json ├── connection-profile │ ├── bank-connection-profile-template.yaml │ ├── bank-connection-profile.yaml │ ├── client-org.yaml │ └── gen-profile.sh ├── connection.js ├── invoke.js ├── package.json ├── query.js └── start.sh ├── chaincode └── src │ ├── bank │ ├── accounts.go │ ├── bank.go │ ├── bank_test.go │ ├── cmd │ │ └── main.go │ ├── deposit.go │ ├── deposit_test.go │ ├── history.go │ ├── transfer.go │ └── transfer_test.go │ ├── forex │ ├── cmd │ │ └── main.go │ ├── forex.go │ └── forex_test.go │ └── interbank │ ├── cmd │ └── main.go │ ├── interbank.go │ └── interbank_test.go ├── events ├── config.json ├── connection.js ├── invoke.sh ├── listener.js └── package.json ├── images ├── analytics.png ├── endtoend.png ├── interbank-arch.png ├── overview.png ├── quicksight.png └── register.png ├── setup ├── cloudformation │ └── team.yaml ├── config ├── configtx.yaml ├── copy_public_certificates.py ├── docker-compose-cli.yaml ├── private-configtx.yaml ├── s3-handler.sh ├── setup_environment.sh └── setup_fabric_environment.py ├── solution └── transfer.go └── ui ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── browserslist ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── app │ ├── account │ │ ├── account.component.css │ │ ├── account.component.html │ │ ├── account.component.spec.ts │ │ └── account.component.ts │ ├── app-routing.module.ts │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── home │ │ ├── home.component.css │ │ ├── home.component.html │ │ ├── home.component.spec.ts │ │ └── home.component.ts │ ├── login │ │ ├── login.component.css │ │ ├── login.component.html │ │ ├── login.component.spec.ts │ │ └── login.component.ts │ ├── services │ │ ├── account.service.spec.ts │ │ └── account.service.ts │ ├── transactions │ │ ├── transactions.component.css │ │ ├── transactions.component.html │ │ ├── transactions.component.spec.ts │ │ └── transactions.component.ts │ └── transfer │ │ ├── transfer.component.css │ │ ├── transfer.component.html │ │ ├── transfer.component.spec.ts │ │ └── transfer.component.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /ChaincodeOverview.md: -------------------------------------------------------------------------------- 1 | # Bank Transfer Workshop Chaincode 2 | 3 | There are three smart contracts for this workshop: 4 | * Bank - a simple representation of a bank that contains bank accounts 5 | * Forex - a contract for providing currency conversion 6 | * Interbank - a contract for routing payments between banks 7 | 8 | # Bank - BankChaincode 9 | The bank chaincode is comprised of a number of source code files. Bank.go is the main file which contains the Invoke and Init functions as well as structs used throughout the chaincode. The bank must be initialized with a minimum of two parameters, name and an ID. The name is purely a description. The ID is a string which is used to uniquely identify the bank and is used as part of the Interbank contract to route payments between banks. The ID is analogous to a SWIFT Code or a Bank Identifier Code (BIC) code. Optionally, you can include two further parameters: forexChaincode and interbankChaincode. These are the names of the ForexChaincode and InterbankChaincode chaincode installed on the same peer as the BankChaincode that provide foreign currency exchange and interbank transfer functionality. 10 | 11 | The Invoke function provides four functions that can be invoked. These are: 12 | * createAccount - create a new account on the ledger 13 | * queryAccount - retrieve that account from the ledger 14 | * deposit - add funds to an account 15 | * transfer - transfer funds between accounts (at the same bank or between accounts) 16 | 17 | # Forex - ForexChaincode 18 | The forex chaincode is the simplest of the three chaincodes. It maps a currency pair (e.g. CAD:USD) to an exchange rate. It exposes two functions: 19 | * getForexPair - write currency pair to the ledger 20 | * createForexPair - get a pair from the ledger 21 | 22 | #Interbank - InterbankChaincode 23 | The interbank transfer chaincode acts as a router between banks. The bank chaincode can be instantiated with a reference to an interbank contract and that bank can call the interbank contract to transfer funds from one of its accounts to another bank. It does this by storing a mapping between bank IDs and bank contracts. When a transfer is initiated, the interbank chaincode looks up the ID of the recieving bank, retrives the contract for the recieving bank and pays money to the account at that bank. If the currency differs, it will invoke a ForexChaincode instance to convert the currency. 24 | 25 | Interbank chaincdoe exposes two functions: 26 | * interbankTransfer - perform a transfer between banks 27 | * registerRoute - map a bank ID to that bank's chaincode 28 | 29 | # Interaction 30 | 31 | The BankChaincode is the base chaincode used to interact with the other chaincodes. You can create 32 | a bank without having to create the others. Without the other chaincode you will only be able to 33 | transfer funds within the bank and using the same currency. By adding a ForexChaincode you can (once 34 | the appropirate forex pairs have been added) perform intrabank transfers across different 35 | currencies. Once you've added an InterbankChaindode and registered an route to another bank, you can 36 | subsequently make interbank transfers. Transfers are initiated using the transer function on the 37 | BankChaincode. If the Bank ID belongs to another bank or the currency symbol differs between 38 | accounts the transfer method will invoke the other contracts - if provided, otherwise it will return 39 | an error. 40 | 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | .DS_Store 3 | client-org1.yaml 4 | ngo-connection-profile.yaml 5 | /certs -------------------------------------------------------------------------------- /api/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | 'use strict'; 18 | const connection = require('./connection.js'); 19 | const query = require('./query.js'); 20 | const invoke = require('./invoke.js'); 21 | const WebSocketServer = require('ws'); 22 | const express = require('express'); 23 | const bodyParser = require('body-parser'); 24 | const http = require('http'); 25 | const util = require('util'); 26 | const app = express(); 27 | const cors = require('cors'); 28 | const hfc = require('fabric-client'); 29 | const uuidv4 = require('uuid/v4'); 30 | const log4js = require('log4js'); 31 | const username = 'admin'; 32 | 33 | log4js.configure({ 34 | appenders: { 35 | out: { type: 'stdout' }, 36 | }, 37 | categories: { 38 | default: { appenders: ['out'], level: 'info' }, 39 | } 40 | }); 41 | 42 | var logger = log4js.getLogger('BANKAPI'); 43 | hfc.addConfigFile('config.json'); 44 | const config = require('./config.json'); 45 | const host = 'localhost'; 46 | const port = 8081; 47 | const orgName = config.org 48 | 49 | var channelName = hfc.getConfigSetting('channelName'); 50 | var chaincodeName = hfc.getConfigSetting('chaincodeName'); 51 | var peers = hfc.getConfigSetting('peers'); 52 | 53 | /////////////////////////////////////////////////////////////////////////////// 54 | //////////////////////////////// SET CONFIGURATIONS /////////////////////////// 55 | /////////////////////////////////////////////////////////////////////////////// 56 | app.options('*', cors()); 57 | app.use(cors()); 58 | app.use(bodyParser.json()); 59 | app.use(bodyParser.urlencoded({ 60 | extended: false 61 | })); 62 | app.use(function(req, res, next) { 63 | logger.info(' ##### New request for URL %s', req.originalUrl); 64 | return next(); 65 | }); 66 | 67 | //wrapper to handle errors thrown by async functions. We can catch all 68 | //errors thrown by async functions in a single place, here in this function, 69 | //rather than having a try-catch in every function below. The 'next' statement 70 | //used here will invoke the error handler function - see the end of this script 71 | const awaitHandler = (fn) => { 72 | return async(req, res, next) => { 73 | try { 74 | await fn(req, res, next) 75 | } 76 | catch (err) { 77 | next(err) 78 | } 79 | } 80 | } 81 | 82 | /////////////////////////////////////////////////////////////////////////////// 83 | //////////////////////////////// START SERVER ///////////////////////////////// 84 | /////////////////////////////////////////////////////////////////////////////// 85 | var server = http.createServer(app).listen(port, function() {}); 86 | logger.info('****************** SERVER STARTED ************************'); 87 | logger.info('*************** Listening on: http://%s:%s ******************', host, port); 88 | server.timeout = 240000; 89 | 90 | function getErrorMessage(field) { 91 | var response = { 92 | success: false, 93 | message: field + ' field is missing or Invalid in the request' 94 | }; 95 | return response; 96 | } 97 | 98 | app.get('/health', awaitHandler(async(req, res) => { 99 | res.sendStatus(200); 100 | })); 101 | 102 | 103 | // account return the details of the account, it invokes the queryAccount chaincode function 104 | app.get('/account/:accNumber', awaitHandler(async(req, res) => { 105 | let args = req.params; 106 | let fcn = "queryAccount"; 107 | //username = req.params["accNumber"]; 108 | 109 | let response = await connection.getRegisteredUser(username, orgName, true); 110 | 111 | logger.info('##### GET account details - username : ' + username); 112 | logger.info('##### GET account details - userOrg : ' + orgName); 113 | logger.info('##### GET account details - channelName : ' + channelName); 114 | logger.info('##### GET account details - chaincodeName : ' + chaincodeName); 115 | logger.info('##### GET account details - fcn : ' + fcn); 116 | logger.info('##### GET account details - args : ' + JSON.stringify(args)); 117 | logger.info('##### GET account details - peers : ' + peers); 118 | 119 | res.header("Access-Control-Allow-Origin", "*"); 120 | let message = await query.queryChaincode(peers, channelName, chaincodeName, [req.params["accNumber"]], fcn, username, orgName); 121 | res.send(message[0]); 122 | })); 123 | 124 | 125 | // transactions returns the history of an account, it invokes the getTransactionHistory chaincode function 126 | app.get('/transactions/:accNumber', awaitHandler(async(req, res) => { 127 | let args = req.params; 128 | let fcn = "getTransactionHistory"; 129 | //username = req.params["accNumber"]; 130 | 131 | let response = await connection.getRegisteredUser(username, orgName, true); 132 | 133 | logger.info('##### GET account details - username : ' + username); 134 | logger.info('##### GET account details - userOrg : ' + orgName); 135 | logger.info('##### GET account details - channelName : ' + channelName); 136 | logger.info('##### GET account details - chaincodeName : ' + chaincodeName); 137 | logger.info('##### GET account details - fcn : ' + fcn); 138 | logger.info('##### GET account details - args : ' + JSON.stringify(args)); 139 | logger.info('##### GET account details - peers : ' + peers); 140 | 141 | let message = await query.queryChaincode(peers, channelName, chaincodeName, [req.params["accNumber"]], fcn, username, orgName); 142 | res.send(Object.values(message[0].History)); 143 | })); 144 | 145 | // the transfer method invokes the transfer chaincode function to perform an intra or interbank transfer 146 | app.post('/transfer', awaitHandler(async(req, res) => { 147 | var args = req.body; 148 | var fcn = "transfer"; 149 | 150 | logger.info('================ POST on transfer'); 151 | logger.info('##### POST for transfer - username : ' + username); 152 | logger.info('##### POST for transfer - userOrg : ' + orgName); 153 | logger.info('##### POST for transfer - channelName : ' + channelName); 154 | logger.info('##### POST for transfer - chaincodeName : ' + chaincodeName); 155 | logger.info('##### POST for transfer - fcn : ' + fcn); 156 | logger.info('##### POST for transfer - args : ' + JSON.stringify(args)) 157 | logger.info('##### POST for transfer - peers : ' + peers); 158 | 159 | var array_args = [] 160 | array_args[0] = args['FromAccNumber'] 161 | array_args[1] = args['ToBankID'] 162 | array_args[2] = args['ToAccNumber'] 163 | array_args[3] = String(args['Amount']) 164 | 165 | let message = await invoke.invokeChaincode(peers, channelName, chaincodeName, array_args, fcn, username, orgName); 166 | res.send(message); 167 | })); 168 | 169 | 170 | 171 | app.use(function(error, req, res, next) { 172 | res.status(500).json({ error: error.toString() }); 173 | }); 174 | -------------------------------------------------------------------------------- /api/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "host":"localhost", 3 | "port":"3000", 4 | "channelName":"ourchannel", 5 | "chaincodeName":"YOUR-BANK-NAME", 6 | "eventWaitTime":"30000", 7 | "clientConfig": "../tmp/connection-profile/Org1/client-Org1.yaml", 8 | "org": "Org1", 9 | "peers":[ 10 | "peer1" 11 | ], 12 | "admins":[ 13 | { 14 | "username":"admin", 15 | "secret":"Admin123" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /api/connection-profile/bank-connection-profile-template.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). 4 | # You may not use this file except in compliance with the License. 5 | # A copy of the License is located at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | name: "bank" 15 | x-type: "hlfv1" 16 | description: "Interbank Network" 17 | version: "1.0" 18 | 19 | channels: 20 | %CHANNEL%: 21 | orderers: 22 | - orderer.com 23 | peers: 24 | peer1: 25 | endorsingPeer: true 26 | chaincodeQuery: true 27 | ledgerQuery: true 28 | eventSource: true 29 | 30 | organizations: 31 | %ORG%: 32 | mspid: %MEMBERID% 33 | peers: 34 | - peer1 35 | certificateAuthorities: 36 | - ca-%ORG% 37 | 38 | orderers: 39 | orderer.com: 40 | url: grpcs://%ORDERINGSERVICEENDPOINT% 41 | grpcOptions: 42 | ssl-target-name-override: %ORDERINGSERVICEENDPOINTNOPORT% 43 | tlsCACerts: 44 | path: %CAFILE% 45 | 46 | peers: 47 | peer1: 48 | url: grpcs://%PEERSERVICEENDPOINT% 49 | eventUrl: grpcs://%PEEREVENTENDPOINT% 50 | grpcOptions: 51 | ssl-target-name-override: %PEERSERVICEENDPOINTNOPORT% 52 | tlsCACerts: 53 | path: %CAFILE% 54 | 55 | certificateAuthorities: 56 | ca-%ORG%: 57 | url: https://%CASERVICEENDPOINT% 58 | httpOptions: 59 | verify: false 60 | tlsCACerts: 61 | path: %CAFILE% 62 | registrar: 63 | - enrollId: %ADMINUSER% 64 | enrollSecret: %ADMINPWD% 65 | caName: %MEMBERID% -------------------------------------------------------------------------------- /api/connection-profile/bank-connection-profile.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). 4 | # You may not use this file except in compliance with the License. 5 | # A copy of the License is located at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | name: "bank" 15 | x-type: "hlfv1" 16 | description: "Interbank Network" 17 | version: "1.0" 18 | 19 | channels: 20 | mychannel: 21 | orderers: 22 | - orderer.com 23 | peers: 24 | peer1: 25 | endorsingPeer: true 26 | chaincodeQuery: true 27 | ledgerQuery: true 28 | eventSource: true 29 | 30 | organizations: 31 | Org1: 32 | mspid: %MEMBERID% 33 | peers: 34 | - peer1 35 | certificateAuthorities: 36 | - ca-%ORG% 37 | 38 | orderers: 39 | orderer.com: 40 | url: grpcs://%ORDERINGSERVICEENDPOINT% 41 | grpcOptions: 42 | ssl-target-name-override: %ORDERINGSERVICEENDPOINTNOPORT% 43 | tlsCACerts: 44 | path: %CAFILE% 45 | 46 | peers: 47 | peer1: 48 | url: grpcs://%PEERSERVICEENDPOINT% 49 | eventUrl: grpcs://%PEEREVENTENDPOINT% 50 | grpcOptions: 51 | ssl-target-name-override: %PEERSERVICEENDPOINTNOPORT% 52 | tlsCACerts: 53 | path: %CAFILE% 54 | 55 | certificateAuthorities: 56 | ca-%ORG%: 57 | url: https://%CASERVICEENDPOINT% 58 | httpOptions: 59 | verify: false 60 | tlsCACerts: 61 | path: %CAFILE% 62 | registrar: 63 | - enrollId: %ADMINUSER% 64 | enrollSecret: %ADMINPWD% 65 | caName: %MEMBERID% -------------------------------------------------------------------------------- /api/connection-profile/client-org.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). 4 | # You may not use this file except in compliance with the License. 5 | # A copy of the License is located at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | name: "bank" 15 | x-type: "hlfv1" 16 | description: "Bank client definition" 17 | version: "1.0" 18 | client: 19 | organization: Org1 20 | credentialStore: 21 | path: "./fabric-client-certs" 22 | cryptoStore: 23 | path: "./fabric-client-keys" -------------------------------------------------------------------------------- /api/connection-profile/gen-profile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"). 6 | # You may not use this file except in compliance with the License. 7 | # A copy of the License is located at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed 12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | # express or implied. See the License for the specific language governing 14 | # permissions and limitations under the License. 15 | 16 | # REPODIR points to this repo 17 | # LOCALCA points to the location of the TLS cert 18 | REPODIR=/home/ec2-user/environment/bank-transfer-blockchain-reinvent2019-workshop 19 | LOCALCA=/home/ec2-user/managedblockchain-tls-chain.pem 20 | ORG=Org1 21 | 22 | ADMINPWD=Admin123 23 | 24 | #copy the connection profiles 25 | mkdir -p $REPODIR/tmp/connection-profile/$ORG 26 | cp $REPODIR/api/connection-profile/bank-connection-profile-template.yaml $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 27 | cp $REPODIR/api/connection-profile/client-org.yaml $REPODIR/tmp/connection-profile/$ORG/client-$ORG.yaml 28 | 29 | #update the connection profiles with endpoints and other information 30 | sed -i "s|%PEERNODEID%|$PEERNODEID|g" $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 31 | echo $MEMBERID 32 | sed -i "s|%ORG%|$ORG|g" $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 33 | sed -i "s|%CHANNEL%|$CHANNEL|g" $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 34 | sed -i "s|%MEMBERID%|$MEMBERID|g" $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 35 | sed -i "s|%CAFILE%|$LOCALCA|g" $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 36 | sed -i "s|%ORDERINGSERVICEENDPOINT%|$ORDERINGSERVICEENDPOINT|g" $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 37 | sed -i "s|%ORDERINGSERVICEENDPOINTNOPORT%|$ORDERINGSERVICEENDPOINTNOPORT|g" $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 38 | sed -i "s|%PEERSERVICEENDPOINT%|$PEERSERVICEENDPOINT|g" $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 39 | sed -i "s|%PEERSERVICEENDPOINTNOPORT%|$PEERSERVICEENDPOINTNOPORT|g" $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 40 | sed -i "s|%PEEREVENTENDPOINT%|$PEEREVENTENDPOINT|g" $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 41 | sed -i "s|%CASERVICEENDPOINT%|$CASERVICEENDPOINT|g" $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 42 | sed -i "s|%ADMINUSER%|$ADMINUSER|g" $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 43 | sed -i "s|%ADMINPWD%|$ADMINPWD|g" $REPODIR/tmp/connection-profile/bank-connection-profile.yaml 44 | 45 | ls -lR $REPODIR/tmp/connection-profile 46 | -------------------------------------------------------------------------------- /api/connection.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | 'use strict'; 18 | var log4js = require('log4js'); 19 | var logger = log4js.getLogger('Connection'); 20 | var util = require('util'); 21 | var hfc = require('fabric-client'); 22 | hfc.setLogger(logger); 23 | 24 | async function getClientForOrg(userorg, username) { 25 | logger.info('============ START getClientForOrg for org %s and user %s', userorg, username); 26 | let config = '../tmp/connection-profile/bank-connection-profile.yaml'; 27 | let orgLower = userorg; 28 | let clientConfig = '../tmp/connection-profile/' + orgLower + '/client-' + orgLower + '.yaml'; 29 | 30 | 31 | logger.info('##### getClient - Loading connection profiles from file: %s and %s', config, clientConfig); 32 | 33 | // Load the connection profiles. First load the network settings, then load the client specific settings 34 | let client = hfc.loadFromConfig(config); 35 | client.loadFromConfig(clientConfig); 36 | 37 | // Create the state store and the crypto store 38 | await client.initCredentialStores(); 39 | 40 | // Try and obtain the user from persistence if the user has previously been 41 | // registered and enrolled 42 | if (username) { 43 | let user = await client.getUserContext(username, true); 44 | if (!user) { 45 | throw new Error(util.format('##### getClient - User was not found :', username)); 46 | } 47 | else { 48 | logger.info('##### getClient - User %s was found to be registered and enrolled', username); 49 | } 50 | } 51 | logger.info('============ END getClientForOrg for org %s and user %s \n\n', userorg, username); 52 | 53 | return client; 54 | } 55 | var getLogger = function(moduleName) { 56 | var logger = log4js.getLogger(moduleName); 57 | return logger; 58 | }; 59 | 60 | var getRegisteredUser = async function(username, userorg, isJson) { 61 | logger.info('============ START getRegisteredUser - for org %s and user %s', userorg, username); 62 | try { 63 | var client = await getClientForOrg(userorg); 64 | var user = await client.getUserContext(username, true); 65 | if (user && user.isEnrolled()) { 66 | logger.info('##### getRegisteredUser - User %s already enrolled', username); 67 | } 68 | else { 69 | // user was not enrolled, so we will need an admin user object to register 70 | logger.info('##### getRegisteredUser - User %s was not enrolled, so we will need an admin user object to register', username); 71 | logger.info('##### getRegisteredUser - Got hfc %s', util.inspect(hfc)); 72 | var admins = hfc.getConfigSetting('admins'); 73 | logger.info('##### getRegisteredUser - Got admin property %s', util.inspect(admins)); 74 | let adminUserObj = await client.setUserContext({ username: admins[0].username, password: admins[0].secret }); 75 | logger.info('##### getRegisteredUser - Got adminUserObj property %s', util.inspect(admins)); 76 | let caClient = client.getCertificateAuthority(); 77 | logger.info('##### getRegisteredUser - Got caClient %s', util.inspect(admins)); 78 | let secret = await caClient.register({ 79 | enrollmentID: username 80 | }, adminUserObj); 81 | logger.info('##### getRegisteredUser - Successfully got the secret for user %s', username); 82 | user = await client.setUserContext({ username: username, password: secret }); 83 | logger.info('##### getRegisteredUser - Successfully enrolled username %s and setUserContext on the client object', username); 84 | } 85 | if (user && user.isEnrolled) { 86 | if (isJson && isJson === true) { 87 | var response = { 88 | success: true, 89 | secret: user._enrollmentSecret, 90 | message: username + ' enrolled Successfully', 91 | }; 92 | return response; 93 | } 94 | } 95 | else { 96 | throw new Error('##### getRegisteredUser - User was not enrolled '); 97 | } 98 | } 99 | catch (error) { 100 | logger.error('##### getRegisteredUser - Failed to get registered user: %s with error: %s', username, error.toString()); 101 | return 'failed ' + error.toString(); 102 | } 103 | }; 104 | 105 | 106 | exports.getRegisteredUser = getRegisteredUser; 107 | exports.getClientForOrg = getClientForOrg; 108 | exports.getLogger = getLogger; 109 | -------------------------------------------------------------------------------- /api/invoke.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | 'use strict'; 18 | var util = require('util'); 19 | var helper = require('./connection.js'); 20 | var logger = helper.getLogger('Invoke'); 21 | 22 | var invokeChaincode = async function(peerNames, channelName, chaincodeName, args, fcn, username, orgName) { 23 | logger.info(util.format('\n============ invokeChaincode - chaincode %s, function %s, on the channel \'%s\' for org: %s\n', 24 | chaincodeName, fcn, channelName, orgName)); 25 | var error_message = null; 26 | var txIdAsString = null; 27 | try { 28 | // first setup the client for this org 29 | var client = await helper.getClientForOrg(orgName, username); 30 | logger.info('##### invokeChaincode - Successfully got the fabric client for the organization "%s"', orgName); 31 | var channel = client.getChannel(channelName); 32 | if(!channel) { 33 | let message = util.format('##### invokeChaincode - Channel %s was not defined in the connection profile', channelName); 34 | logger.error(message); 35 | throw new Error(message); 36 | } 37 | var txId = client.newTransactionID(); 38 | txIdAsString = txId.getTransactionID(); 39 | 40 | // send proposal to endorsing peers 41 | var request = { 42 | targets: peerNames, 43 | chaincodeId: chaincodeName, 44 | fcn: fcn, 45 | args: args, 46 | chainId: channelName, 47 | txId: txId 48 | }; 49 | 50 | logger.info('##### invokeChaincode - Invoke transaction request to Fabric %s', JSON.stringify(request)); 51 | let results = await channel.sendTransactionProposal(request); 52 | 53 | // the returned object has both the endorsement results 54 | // and the actual proposal, the proposal will be needed 55 | // later when we send a transaction to the ordering service 56 | var proposalResponses = results[0]; 57 | var proposal = results[1]; 58 | 59 | // lets have a look at the responses to see if they are 60 | // all good, if good they will also include signatures 61 | // required to be committed 62 | var successfulResponses = true; 63 | for (var i in proposalResponses) { 64 | let oneSuccessfulResponse = false; 65 | if (proposalResponses && proposalResponses[i].response && 66 | proposalResponses[i].response.status === 200) { 67 | oneSuccessfulResponse = true; 68 | logger.info('##### invokeChaincode - received successful proposal response'); 69 | } else { 70 | logger.error('##### invokeChaincode - received unsuccessful proposal response'); 71 | } 72 | successfulResponses = successfulResponses & oneSuccessfulResponse; 73 | } 74 | 75 | if (successfulResponses) { 76 | logger.info(util.format( 77 | '##### invokeChaincode - Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s"', 78 | proposalResponses[0].response.status, proposalResponses[0].response.message)); 79 | 80 | // wait for the channel-based event hub to tell us 81 | // that the commit was good or bad on each peer in our organization 82 | var promises = []; 83 | let event_hubs = channel.getChannelEventHubsForOrg(); 84 | event_hubs.forEach((eh) => { 85 | logger.info('##### invokeChaincode - invokeEventPromise - setting up event handler'); 86 | let invokeEventPromise = new Promise((resolve, reject) => { 87 | let event_timeout = setTimeout(() => { 88 | let message = 'REQUEST_TIMEOUT:' + eh.getPeerAddr(); 89 | logger.error(message); 90 | eh.disconnect(); 91 | }, 10000); 92 | eh.registerTxEvent(txIdAsString, (tx, code, block_num) => { 93 | logger.info('##### invokeChaincode - The invoke chaincode transaction has been committed on peer %s',eh.getPeerAddr()); 94 | logger.info('##### invokeChaincode - Transaction %s has status of %s in block %s', tx, code, block_num); 95 | clearTimeout(event_timeout); 96 | 97 | if (code !== 'VALID') { 98 | let message = util.format('##### invokeChaincode - The invoke chaincode transaction was invalid, code:%s',code); 99 | logger.error(message); 100 | reject(new Error(message)); 101 | } else { 102 | let message = '##### invokeChaincode - The invoke chaincode transaction was valid.'; 103 | logger.info(message); 104 | resolve(message); 105 | } 106 | }, (err) => { 107 | clearTimeout(event_timeout); 108 | logger.error(err); 109 | reject(err); 110 | }, 111 | // the default for 'unregister' is true for transaction listeners 112 | // so no real need to set here, however for 'disconnect' 113 | // the default is false as most event hubs are long running 114 | // in this use case we are using it only once 115 | {unregister: true, disconnect: true} 116 | ); 117 | eh.connect(); 118 | }); 119 | promises.push(invokeEventPromise); 120 | }); 121 | 122 | var orderer_request = { 123 | txId: txId, 124 | proposalResponses: proposalResponses, 125 | proposal: proposal 126 | }; 127 | var sendPromise = channel.sendTransaction(orderer_request); 128 | // put the send to the ordering service last so that the events get registered and 129 | // are ready for the orderering and committing 130 | promises.push(sendPromise); 131 | let results = await Promise.all(promises); 132 | logger.info(util.format('##### invokeChaincode ------->>> R E S P O N S E : %j', results)); 133 | let response = results.pop(); // ordering service results are last in the results 134 | if (response.status === 'SUCCESS') { 135 | logger.info('##### invokeChaincode - Successfully sent transaction to the ordering service.'); 136 | } else { 137 | error_message = util.format('##### invokeChaincode - Failed to order the transaction. Error code: %s',response.status); 138 | logger.info(error_message); 139 | } 140 | 141 | // now see what each of the event hubs reported 142 | for(let i in results) { 143 | let event_hub_result = results[i]; 144 | let event_hub = event_hubs[i]; 145 | logger.info('##### invokeChaincode - Event results for event hub :%s',event_hub.getPeerAddr()); 146 | if(typeof event_hub_result === 'string') { 147 | logger.info('##### invokeChaincode - ' + event_hub_result); 148 | } 149 | else { 150 | if (!error_message) error_message = event_hub_result.toString(); 151 | logger.info('##### invokeChaincode - ' + event_hub_result.toString()); 152 | } 153 | } 154 | } 155 | else { 156 | error_message = util.format('##### invokeChaincode - Failed to send Proposal and receive all good ProposalResponse. Status code: ' + 157 | proposalResponses[0].status + ', ' + 158 | proposalResponses[0].message + '\n' + 159 | proposalResponses[0].stack); 160 | logger.info(error_message); 161 | } 162 | } 163 | catch (error) { 164 | logger.error('##### invokeChaincode - Failed to invoke due to error: ' + error.stack ? error.stack : error); 165 | error_message = error.toString(); 166 | } 167 | 168 | if (!error_message) { 169 | let message = util.format( 170 | '##### invokeChaincode - Successfully invoked chaincode %s, function %s, on the channel \'%s\' for org: %s and transaction ID: %s', 171 | chaincodeName, fcn, channelName, orgName, txIdAsString); 172 | logger.info(message); 173 | let response = {}; 174 | response.transactionId = txIdAsString; 175 | return response; 176 | } 177 | else { 178 | let message = util.format('##### invokeChaincode - Failed to invoke chaincode. cause:%s', error_message); 179 | logger.error(message); 180 | throw new Error(message); 181 | } 182 | }; 183 | 184 | exports.invokeChaincode = invokeChaincode; 185 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "description": "An API for the Blockchain Interbank Transfer Workshop", 5 | "main": "app.js", 6 | "dependencies": { 7 | "aws-sdk": "^2.580.0", 8 | "body-parse": "^0.1.0", 9 | "body-parser": "^1.19.0", 10 | "cookie-parser": "^1.4.4", 11 | "cors": "^2.8.5", 12 | "express": "^4.17.1", 13 | "fabric-ca-client": "^1.4.4", 14 | "fabric-client": "^1.4.4", 15 | "log4js": "^6.1.0", 16 | "uuid": "^3.3.3", 17 | "ws": "^7.2.0" 18 | }, 19 | "devDependencies": {}, 20 | "scripts": { 21 | "test": "mocha" 22 | }, 23 | "author": "", 24 | "license": "MIT" 25 | } 26 | -------------------------------------------------------------------------------- /api/query.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | */ 15 | 16 | var util = require('util'); 17 | var helper = require('./connection.js'); 18 | var logger = helper.getLogger('Query'); 19 | 20 | var queryChaincode = async function(peers, channelName, chaincodeName, args, fcn, username, orgName) { 21 | try { 22 | // setup the client for this org 23 | var client = await helper.getClientForOrg(orgName, username); 24 | logger.info('============ START queryChaincode - Successfully got the fabric client for the organization "%s"', orgName); 25 | var channel = client.getChannel(channelName); 26 | if(!channel) { 27 | let message = util.format('##### queryChaincode - Channel %s was not defined in the connection profile', channelName); 28 | logger.error(message); 29 | throw new Error(message); 30 | } 31 | logger.info("args:" + JSON.stringify(args)) 32 | // send query 33 | var request = { 34 | targets : peers, 35 | chaincodeId: chaincodeName, 36 | fcn: fcn, 37 | args: args 38 | }; 39 | 40 | logger.info('##### queryChaincode - Query request to Fabric %s', JSON.stringify(request)); 41 | let responses = await channel.queryByChaincode(request); 42 | let ret = []; 43 | if (responses) { 44 | // you may receive multiple responses if you passed in multiple peers. For example, 45 | // if the targets : peers in the request above contained 2 peers, you should get 2 responses 46 | for (let i = 0; i < responses.length; i++) { 47 | logger.info(responses) 48 | logger.info(responses[0]) 49 | logger.info('##### queryChaincode - result of query: ' + responses[i].toString('utf8') + '\n'); 50 | } 51 | // check for error 52 | let response = responses[0].toString('utf8'); 53 | logger.info('##### queryChaincode - type of response: %s', typeof response); 54 | if (responses[0].toString('utf8').indexOf("Error: transaction returned with failure") != -1) { 55 | let message = util.format('##### queryChaincode - error in query result: %s', responses[0].toString('utf8')); 56 | logger.error(message); 57 | throw new Error(message); 58 | } 59 | // we will only use the first response. We strip out the Fabric key and just return the payload 60 | // 61 | logger.info(responses) 62 | let json = JSON.parse(responses[0].toString('utf8')); 63 | logger.info('##### queryChaincode - Query json %s', util.inspect(json)); 64 | if (Array.isArray(json)) { 65 | for (let key in json) { 66 | if (json[key]['Record']) { 67 | ret.push(json[key]['Record']); 68 | } 69 | else { 70 | ret.push(json[key]); 71 | } 72 | } 73 | } 74 | else { 75 | ret.push(json); 76 | } 77 | return ret; 78 | } 79 | else { 80 | logger.error('##### queryChaincode - result of query, responses is null'); 81 | return 'responses is null'; 82 | } 83 | } 84 | catch(error) { 85 | logger.error('##### queryChaincode - Failed to query due to error: ' + error.stack ? error.stack : error); 86 | return error.toString(); 87 | } 88 | }; 89 | 90 | exports.queryChaincode = queryChaincode; 91 | 92 | -------------------------------------------------------------------------------- /api/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"). 6 | # You may not use this file except in compliance with the License. 7 | # A copy of the License is located at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed 12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | # express or implied. See the License for the specific language governing 14 | # permissions and limitations under the License. 15 | 16 | # Clears the key stores before starting the Node app 17 | 18 | rm -rf /tmp/fabric-client-kv-org1/ 19 | rm -rf fabric-client-kv-org1/ 20 | rm -rf /tmp/fabric-client-kv-org2/ 21 | rm -rf fabric-client-kv-org2/ 22 | node app.js -------------------------------------------------------------------------------- /chaincode/src/bank/accounts.go: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | package bank 18 | 19 | import ( 20 | "encoding/json" 21 | "github.com/hyperledger/fabric/core/chaincode/shim" 22 | sc "github.com/hyperledger/fabric/protos/peer" 23 | "github.com/shopspring/decimal" 24 | ) 25 | 26 | // createAccount creates a new bank account at this bank 27 | //Args: 28 | // Name string The customer name 29 | // AccNumber string The account number 30 | // Balance decimal.Decimal The account balance 31 | // Currency string The three decimal currency code for the account 32 | 33 | func (s *BankChaincode) createAccount(stub shim.ChaincodeStubInterface, args []string) sc.Response { 34 | if len(args) != 4 { 35 | return shim.Error("Incorrect arguments, expecting customer name, account number, balance and, currency") 36 | } 37 | 38 | balance, decimalError := decimal.NewFromString(args[2]) 39 | 40 | if decimalError != nil { 41 | return shim.Error("Unable to parse account balance") 42 | } 43 | 44 | account := account{Name: args[0], AccNumber: args[1], Balance: balance, Currency: args[3]} 45 | 46 | //serialize account 47 | accountBytes, _ := json.Marshal(account) 48 | 49 | //Add account to ledger 50 | putStateErr := stub.PutState(args[1], accountBytes) 51 | 52 | if putStateErr != nil { 53 | return shim.Error("Failed to create bank") 54 | } 55 | 56 | return shim.Success(nil) 57 | 58 | } 59 | 60 | // queryAccount returns a record for an account 61 | //Args: 62 | // AccNumber string The account number 63 | func (s *BankChaincode) queryAccount(stub shim.ChaincodeStubInterface, args []string) sc.Response { 64 | 65 | if len(args) != 1 { 66 | return shim.Error("Incorrect number of arguments. Expecting the account number") 67 | } 68 | 69 | accountAsBytes, stubError := stub.GetState(args[0]) 70 | 71 | if stubError != nil { 72 | return shim.Error(stubError.Error()) 73 | 74 | } 75 | 76 | return (shim.Success(accountAsBytes)) 77 | } 78 | -------------------------------------------------------------------------------- /chaincode/src/bank/bank.go: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | package bank 18 | 19 | import ( 20 | "encoding/json" 21 | "github.com/hyperledger/fabric/core/chaincode/shim" 22 | sc "github.com/hyperledger/fabric/protos/peer" 23 | "github.com/shopspring/decimal" 24 | ) 25 | 26 | //BankChaincode is the struct that all chaincode methods are associated with 27 | //The bank chaincode provides a simple representation of a bank. It allows for the creation of bank 28 | //accounts and the transfer of funds between bank accounts. There are several functions: 29 | // init - initialize the chaincode 30 | // invoke - called upon invocation and calls other functions 31 | // createAccount - create a bank account 32 | // deposit - deposit funds into a bank account 33 | // transfer - transfer funds between accounts, either interbank or intrabank 34 | type BankChaincode struct { 35 | } 36 | 37 | // bank is a struct that represents a bank, it is stored on the ledeger under the key "bank" 38 | //Name string - the name of the bank 39 | //ID string - the ID of the bank, used to route between banks, analogous to an IBAN or SWIFT code 40 | //ForexContract - the name of a ForexChaincode, deployed to the same peer as the bank, use to provide intrabank currency exchange 41 | //InterbankContract - the name of the InterbankChaincode, used to transfer funds between banks 42 | // A bank contract must be initalized with and name and ID. The two contracts are optional but required to do interbank transfers 43 | //and interbank currency exchange - without them these will produce an error. 44 | type bank struct { 45 | Name string `json:"name"` 46 | ID string `json:"bankID"` 47 | ForexContract string `json:"forexContract"` 48 | InterbankContract string `json:"interbankContract"` 49 | } 50 | 51 | type account struct { 52 | Name string `json:"name"` 53 | AccNumber string `json:"id"` 54 | Balance decimal.Decimal `json:"balance"` 55 | Currency string `json:"currency"` 56 | } 57 | 58 | type forexPair struct { 59 | Pair string `json:"pair"` 60 | Rate float64 `json:"rate"` 61 | } 62 | 63 | //Init method is run on chaincode installation and upgrade 64 | //Args: 65 | // Name string The Name of the Bank 66 | // ID string The institution ID of the bank, (e.g. IBAN, SWIFT or other routing code) 67 | // ForexContract string The name of the contract that provides Forex services to this bank 68 | // InterbankContract string The name of the contrat providing interbank transfer to this bank 69 | func (s *BankChaincode) Init(stub shim.ChaincodeStubInterface) sc.Response { 70 | args := stub.GetStringArgs() 71 | if len(args) < 2 { 72 | return shim.Error("Incorrect arguments. Expecting a bank name, ID. Optionally and the name of the ForexContract and the name of the InterBank contract") 73 | } 74 | 75 | name := args[0] 76 | id := args[1] 77 | forexContract := "" 78 | interbankContract := "" 79 | 80 | if len(args) > 2 { 81 | forexContract = args[2] 82 | } 83 | 84 | if len(args) > 3 { 85 | interbankContract = args[3] 86 | } 87 | 88 | bank := bank{Name: name, ID: id, ForexContract: forexContract, InterbankContract: interbankContract} 89 | 90 | bankBytes, _ := json.Marshal(bank) 91 | err := stub.PutState("bank", bankBytes) 92 | 93 | if err != nil { 94 | return shim.Error(err.Error()) 95 | } 96 | 97 | return shim.Success(nil) 98 | } 99 | 100 | //Invoke is called when external applications invoke the smart contract 101 | func (s *BankChaincode) Invoke(stub shim.ChaincodeStubInterface) sc.Response { 102 | function, args := stub.GetFunctionAndParameters() 103 | 104 | if function == "createAccount" { 105 | return s.createAccount(stub, args) 106 | } else if function == "queryAccount" { 107 | return s.queryAccount(stub, args) 108 | } else if function == "transfer" { 109 | return s.transfer(stub, args) 110 | } else if function == "deposit" { 111 | return s.deposit(stub, args) 112 | } else if function == "getTransactionHistory" { 113 | return s.getTransactionHistory(stub, args) 114 | } 115 | 116 | return shim.Error("Invalid function") 117 | } 118 | -------------------------------------------------------------------------------- /chaincode/src/bank/bank_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | package bank 18 | 19 | import ( 20 | "encoding/json" 21 | "forex" 22 | "github.com/google/uuid" 23 | 24 | "github.com/hyperledger/fabric/core/chaincode/shim" 25 | "github.com/shopspring/decimal" 26 | "github.com/stretchr/testify/assert" 27 | "testing" 28 | ) 29 | 30 | func TestQueryCustomer(t *testing.T) { 31 | stub := shim.NewMockStub("TestStub", new(BankChaincode)) 32 | uid := uuid.New().String() 33 | 34 | writeResponse := stub.MockInvoke(uid, [][]byte{[]byte("createAccount"), 35 | []byte("Bob Jones"), []byte("1"), []byte("400"), []byte("USD")}) 36 | 37 | assert.EqualValues(t, shim.OK, writeResponse.GetStatus(), "failed to execute invocation") 38 | 39 | readResponse := stub.MockInvoke(uid, [][]byte{[]byte("queryAccount"), 40 | []byte("1")}) 41 | 42 | assert.EqualValues(t, shim.OK, readResponse.GetStatus(), "failed to execute invocation") 43 | 44 | resonseAccount := &account{} 45 | json.Unmarshal(readResponse.GetPayload(), resonseAccount) 46 | 47 | assert.Equal(t, "1", resonseAccount.AccNumber, "account number mismatch") 48 | 49 | validateBalance, _ := decimal.NewFromString("400") 50 | assert.Equal(t, validateBalance, resonseAccount.Balance, "balance mismatch") 51 | 52 | } 53 | 54 | func TestTransfer(t *testing.T) { 55 | 56 | fx := new(forex.ForexChaincode) 57 | b1 := new(BankChaincode) 58 | 59 | forexStub := shim.NewMockStub("forex", fx) 60 | bankStub := shim.NewMockStub("bank", b1) 61 | 62 | uid := uuid.New().String() 63 | response := forexStub.MockInit(uid, [][]byte{}) 64 | 65 | forexStub.MockPeerChaincode("bank", bankStub) 66 | bankStub.MockPeerChaincode("forex", forexStub) 67 | 68 | //create forexpair 69 | uid = uuid.New().String() 70 | response = forexStub.MockInvoke(uid, [][]byte{[]byte("createUpdateForexPair"), []byte("GBP"), []byte("USD"), []byte("1.20")}) 71 | 72 | uid = uuid.New().String() 73 | 74 | response = bankStub.MockInit(uid, [][]byte{[]byte("CloudBank"), []byte("0001"), []byte("forex")}) 75 | assert.EqualValues(t, shim.OK, response.GetStatus(), response.Message) 76 | 77 | uid = uuid.New().String() 78 | response1 := bankStub.MockInvoke(uid, [][]byte{[]byte("createAccount"), 79 | []byte("Bob Jones"), []byte("1"), []byte("0"), []byte("USD")}) 80 | 81 | assert.EqualValues(t, shim.OK, response1.GetStatus(), "failed to execute invocation") 82 | 83 | uid = uuid.New().String() 84 | response2 := bankStub.MockInvoke(uid, [][]byte{[]byte("createAccount"), 85 | []byte("Jim Smith"), []byte("2"), []byte("100"), []byte("GBP")}) 86 | 87 | assert.EqualValues(t, shim.OK, response2.GetStatus(), "failed to execute invocation") 88 | 89 | //perform transfer 90 | uid = uuid.New().String() 91 | response3 := bankStub.MockInvoke(uid, [][]byte{[]byte("transfer"), []byte("2"), []byte("0001"), []byte("1"), []byte("10")}) 92 | assert.EqualValues(t, shim.OK, response3.GetStatus(), "failed to execute invocation") 93 | 94 | //query from account 95 | uid = uuid.New().String() 96 | response4 := bankStub.MockInvoke(uid, [][]byte{[]byte("queryAccount"), 97 | []byte("2")}) 98 | 99 | assert.EqualValues(t, shim.OK, response4.GetStatus(), "failed to execute invocation") 100 | 101 | resonseAccount := &account{} 102 | err := json.Unmarshal(response4.GetPayload(), resonseAccount) 103 | if err != nil { 104 | panic(err) 105 | } 106 | 107 | validateBalance, _ := decimal.NewFromString("90") 108 | assert.Equal(t, validateBalance, resonseAccount.Balance, "incorrect balance") 109 | 110 | //Query To Account 111 | uid = uuid.New().String() 112 | 113 | response = bankStub.MockInvoke(uid, [][]byte{[]byte("queryAccount"), 114 | []byte("1")}) 115 | 116 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 117 | 118 | resonseAccount = &account{} 119 | 120 | err = json.Unmarshal(response.GetPayload(), resonseAccount) 121 | if err != nil { 122 | panic(err) 123 | } 124 | 125 | validateBalance, _ = decimal.NewFromString("12") 126 | assert.Equal(t, validateBalance, resonseAccount.Balance, "incorrect balance") 127 | } 128 | -------------------------------------------------------------------------------- /chaincode/src/bank/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bank" 5 | "fmt" 6 | "github.com/hyperledger/fabric/core/chaincode/shim" 7 | ) 8 | 9 | func main() { 10 | err := shim.Start(new(bank.BankChaincode)) 11 | if err != nil { 12 | fmt.Printf("Error creating new BankChaincodet: %s", err) 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /chaincode/src/bank/deposit.go: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | package bank 18 | 19 | import ( 20 | "encoding/json" 21 | "github.com/hyperledger/fabric/core/chaincode/shim" 22 | sc "github.com/hyperledger/fabric/protos/peer" 23 | "github.com/shopspring/decimal" 24 | ) 25 | 26 | // deposit adds funds to an account, this is used to receive funds from an interbank transfer 27 | //args 28 | // acc string the account number to deposit funds to 29 | // amount string the amount to deposit 30 | func (s *BankChaincode) deposit(stub shim.ChaincodeStubInterface, args []string) sc.Response { 31 | 32 | accNum := args[0] 33 | amount, err := decimal.NewFromString(args[1]) 34 | 35 | if err != nil { 36 | return shim.Error("Second argument (Amount to deposit) must be a number. Error: " + err.Error()) 37 | } 38 | 39 | if amount.LessThanOrEqual(decimal.NewFromFloat(0.0)) { 40 | return shim.Error("Second argument (Amount to deposit) must be a positive number") 41 | } 42 | 43 | //get the account to operate on 44 | accountAsByes := s.queryAccount(stub, []string{accNum}).Payload 45 | acc := &account{} 46 | err = json.Unmarshal(accountAsByes, acc) 47 | 48 | if err != nil { 49 | return shim.Error("Unable to retrieve to account ID from ledger " + err.Error()) 50 | } 51 | 52 | acc.Balance = acc.Balance.Add(amount) 53 | 54 | // write changes to ledger 55 | accAsBytes, _ := json.Marshal(acc) 56 | err = stub.PutState(acc.AccNumber, accAsBytes) 57 | if err != nil { 58 | return shim.Error("Error trying to commit account to ledger" + err.Error()) 59 | } 60 | 61 | return (shim.Success(nil)) 62 | 63 | } 64 | -------------------------------------------------------------------------------- /chaincode/src/bank/deposit_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | package bank 18 | 19 | import ( 20 | "encoding/json" 21 | "github.com/google/uuid" 22 | "github.com/hyperledger/fabric/core/chaincode/shim" 23 | "github.com/shopspring/decimal" 24 | "github.com/stretchr/testify/assert" 25 | "testing" 26 | ) 27 | 28 | func TestPay(t *testing.T) { 29 | 30 | bankStub := shim.NewMockStub("bank", new(BankChaincode)) 31 | 32 | uid := uuid.New().String() 33 | 34 | response := bankStub.MockInit(uid, [][]byte{[]byte("CloudBank"), []byte("0001"), []byte("forex")}) 35 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 36 | 37 | uid = uuid.New().String() 38 | response = bankStub.MockInvoke(uid, [][]byte{[]byte("createAccount"), 39 | []byte("Bob Jones"), []byte("0001"), []byte("0"), []byte("USD")}) 40 | 41 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 42 | 43 | //perform transfer 44 | uid = uuid.New().String() 45 | response = bankStub.MockInvoke(uid, [][]byte{[]byte("deposit"), []byte("0001"), []byte("500")}) 46 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 47 | 48 | //query from account 49 | uid = uuid.New().String() 50 | response = bankStub.MockInvoke(uid, [][]byte{[]byte("queryAccount"), 51 | []byte("0001")}) 52 | 53 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 54 | 55 | acc := &account{} 56 | 57 | err := json.Unmarshal(response.GetPayload(), acc) 58 | 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | validateBalance, _ := decimal.NewFromString("500") 64 | assert.Equal(t, validateBalance, acc.Balance, "incorrect balance") 65 | } 66 | -------------------------------------------------------------------------------- /chaincode/src/bank/history.go: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | package bank 18 | 19 | import ( 20 | "encoding/json" 21 | "github.com/hyperledger/fabric/core/chaincode/shim" 22 | sc "github.com/hyperledger/fabric/protos/peer" 23 | ) 24 | 25 | type Transaction struct { 26 | Timestamp int64 `json: timestamp` 27 | Value string `json: value` 28 | } 29 | 30 | type Transactions struct { 31 | History []Transaction `json: transactions` 32 | } 33 | 34 | func (s *BankChaincode) getTransactionHistory(stub shim.ChaincodeStubInterface, args []string) sc.Response { 35 | 36 | accNumber := args[0] 37 | 38 | resultsIterator, err := stub.GetHistoryForKey(accNumber) 39 | 40 | if err != nil { 41 | return shim.Error("Unable to get key history " + err.Error()) 42 | } 43 | 44 | transactions := Transactions{History: []Transaction{}} 45 | 46 | for resultsIterator.HasNext() { 47 | queryResponse, err := resultsIterator.Next() 48 | if err != nil { 49 | return shim.Error(err.Error()) 50 | } 51 | 52 | transaction := Transaction{Timestamp: queryResponse.Timestamp.GetSeconds(), Value: string(queryResponse.Value[:])} 53 | transactions.History = append(transactions.History, transaction) 54 | } 55 | 56 | b, err := json.Marshal(transactions) 57 | return shim.Success(b) 58 | } 59 | -------------------------------------------------------------------------------- /chaincode/src/bank/transfer.go: -------------------------------------------------------------------------------- 1 | package bank 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "github.com/hyperledger/fabric/common/util" 7 | "github.com/hyperledger/fabric/core/chaincode/shim" 8 | sc "github.com/hyperledger/fabric/protos/peer" 9 | "github.com/shopspring/decimal" 10 | ) 11 | 12 | type transferEvent struct { 13 | FromAccNumber string `json:"FromAccNumber"` 14 | FromBankID string `json:"FromBankID"` 15 | ToAccNumber string `json:"ToAccNumber"` 16 | ToBankID string `json:"ToBankID"` 17 | Amount string `json:"Amount"` 18 | } 19 | 20 | // Transfer funds from one account to another given four arguments: Payers account Id, Payees bank, 21 | // Payees account Id, and the amount to transfer. The amount must be a positive number. 22 | // If the payer and payee accounts belong to the same bank this will perform an intrabank transfer 23 | // otherwise this will initiate an interbank transfer 24 | // Transferring between accounts at the same bank, but with different currencies requires a Forex contract 25 | // params: fromAccount, toBank, toAccount, amount 26 | func (s *BankChaincode) transfer(stub shim.ChaincodeStubInterface, args []string) sc.Response { 27 | if len(args) != 4 { 28 | return shim.Error("Incorret number of args. Expecting 4: fromAccount, toBank, toAccount, amount") 29 | } 30 | 31 | //sorting arguments 32 | fromAccNum := args[0] 33 | toBankID := args[1] 34 | toAccNum := args[2] 35 | amountAsString := args[3] 36 | amount, err := decimal.NewFromString(args[3]) 37 | 38 | //validate amount is actually a number 39 | if err != nil { 40 | return shim.Error("Unable to parse amount: " + amountAsString) 41 | } 42 | 43 | if amount.LessThanOrEqual(decimal.NewFromFloat(0.0)) { 44 | return shim.Error("Second argument (Amount to deposit) must be a positive number") 45 | } 46 | 47 | //get the fromAccount 48 | fromAccountAsByes := s.queryAccount(stub, []string{fromAccNum}).Payload 49 | fromAccount := &account{} 50 | err = json.Unmarshal(fromAccountAsByes, fromAccount) 51 | 52 | if err != nil { 53 | return shim.Error("Unable to retrieve to account ID from ledger " + err.Error()) 54 | } 55 | 56 | // check if funds are available 57 | if fromAccount.Balance.Cmp(amount) == -1 { 58 | return shim.Error("Account has insufficient funds") 59 | } 60 | 61 | //query our bank to get the ID of the institution 62 | bankAsBytes, err := stub.GetState("bank") 63 | 64 | if err != nil { 65 | return shim.Error("Unable to retrieve bank ID from ledger " + err.Error()) 66 | } 67 | 68 | thisBank := &bank{} 69 | err = json.Unmarshal(bankAsBytes, thisBank) 70 | if err != nil { 71 | return shim.Error("Unable to retrieve bank ID from ledger " + err.Error()) 72 | } 73 | 74 | // is this an interbank transfer? check if recipient bank is not this bank 75 | if toBankID != thisBank.ID { 76 | return shim.Error("Interbank transfer is not yet implemented, either implement it or use the solution") 77 | } 78 | 79 | //if not inter bank transfer, perform an intra bank transfer 80 | toAccountAsByes := s.queryAccount(stub, []string{toAccNum}).Payload 81 | toAccount := &account{} 82 | err = json.Unmarshal(toAccountAsByes, toAccount) 83 | 84 | if err != nil { 85 | return shim.Error("Unable to retrieve to account ID from ledger " + err.Error() + string(toAccountAsByes)) 86 | } 87 | 88 | //check if from and to accounts use the same currency 89 | var exchangeRate decimal.Decimal 90 | if fromAccount.Currency == toAccount.Currency { 91 | exchangeRate = decimal.NewFromFloat(1.0) 92 | } else { 93 | // call handler function to invoke Forex chaincode 94 | exchangeRateAsFloat, err := getCurrencyConversion(stub, thisBank.ForexContract, fromAccount.Currency, toAccount.Currency) 95 | 96 | if err != nil { 97 | return shim.Error("Unable to perform currency conversion:" + err.Error()) 98 | } 99 | 100 | exchangeRate = decimal.NewFromFloat(exchangeRateAsFloat) 101 | } 102 | 103 | //update balances 104 | fromAccount.Balance = fromAccount.Balance.Sub(amount) 105 | toAccount.Balance = toAccount.Balance.Add(amount.Mul(exchangeRate)) 106 | 107 | // write changes to ledger 108 | fromAccountAsByes, _ = json.Marshal(fromAccount) 109 | err = stub.PutState(fromAccNum, fromAccountAsByes) 110 | if err != nil { 111 | return shim.Error("Error trying to commit account to ledger" + err.Error()) 112 | } 113 | 114 | toAccountAsByes, _ = json.Marshal(toAccount) 115 | err = stub.PutState(toAccNum, toAccountAsByes) 116 | if err != nil { 117 | return shim.Error("Error trying to commit account to ledger" + err.Error()) 118 | } 119 | 120 | //write out an event of the transfer 121 | event := &transferEvent{FromAccNumber: fromAccount.AccNumber, FromBankID: thisBank.ID, ToBankID: toBankID, ToAccNumber: toAccNum, Amount: amount.String()} 122 | eventBytes, _ := json.Marshal(event) 123 | stub.SetEvent("transfer-event", eventBytes) 124 | return (shim.Success(nil)) 125 | } 126 | 127 | // } 128 | 129 | func getCurrencyConversion(stub shim.ChaincodeStubInterface, forexContract string, baseCurrency string, counterCurrency string) (float64, error) { 130 | 131 | if forexContract == "" { 132 | return 0.0, errors.New("Forex contract is empty, unable to complete transaction") 133 | } 134 | 135 | // invoke the forex contract to get the exchange rate for the pair 136 | stringArgs := []string{"getForexPair", baseCurrency, counterCurrency} 137 | args := util.ArrayToChaincodeArgs(stringArgs) 138 | response := stub.InvokeChaincode(forexContract, args, "") 139 | 140 | if response.Status != shim.OK { 141 | return 0.0, errors.New("Unable to get exchange rate from Forex Contract" + response.Message) 142 | } 143 | 144 | responseForex := &forexPair{} 145 | err := json.Unmarshal(response.GetPayload(), responseForex) 146 | 147 | if err != nil { 148 | return 0.0, errors.New("Unable to unmarshal exchange rate from Forex Contract" + err.Error()) 149 | } 150 | 151 | return responseForex.Rate, nil 152 | } 153 | -------------------------------------------------------------------------------- /chaincode/src/bank/transfer_test.go: -------------------------------------------------------------------------------- 1 | package bank 2 | 3 | import ( 4 | "encoding/json" 5 | "forex" 6 | "github.com/google/uuid" 7 | "github.com/hyperledger/fabric/common/util" 8 | "interbank" 9 | 10 | "github.com/hyperledger/fabric/core/chaincode/shim" 11 | "github.com/shopspring/decimal" 12 | "github.com/stretchr/testify/assert" 13 | "testing" 14 | ) 15 | 16 | func TestInterbankTransfer(t *testing.T) { 17 | 18 | fx := new(forex.ForexChaincode) 19 | b1 := new(BankChaincode) 20 | b2 := new(BankChaincode) 21 | 22 | ibank := new(interbank.InterbankChaincode) 23 | 24 | forexStub := shim.NewMockStub("forex", fx) 25 | bankStub := shim.NewMockStub("bank", b1) 26 | ibankStub := shim.NewMockStub("ibank", ibank) 27 | bank2stub := shim.NewMockStub("bank2", b2) 28 | 29 | uid := uuid.New().String() 30 | response := forexStub.MockInit(uid, [][]byte{}) 31 | 32 | uid = uuid.New().String() 33 | response = ibankStub.MockInit(uid, [][]byte{}) 34 | 35 | ibankStub.MockPeerChaincode("forex", forexStub) 36 | ibankStub.MockPeerChaincode("bank", bankStub) 37 | ibankStub.MockPeerChaincode("bank2", bank2stub) 38 | 39 | bank2stub.MockPeerChaincode("ibank", ibankStub) 40 | 41 | //create forexpair 42 | uid = uuid.New().String() 43 | stringArgs := []string{"createUpdateForexPair", "GBP", "USD", "1.20"} 44 | args := util.ArrayToChaincodeArgs(stringArgs) 45 | response = forexStub.MockInvoke(uid, args) 46 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 47 | 48 | //create forexpair 49 | uid = uuid.New().String() 50 | stringArgs = []string{"createUpdateForexPair", "USD", "GBP", "0.80"} 51 | args = util.ArrayToChaincodeArgs(stringArgs) 52 | response = forexStub.MockInvoke(uid, args) 53 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 54 | 55 | //create bank 1 56 | uid = uuid.New().String() 57 | response = bankStub.MockInit(uid, [][]byte{[]byte("CloudBank"), []byte("0001"), []byte("forex")}) 58 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 59 | 60 | //create bank 2 61 | uid = uuid.New().String() 62 | response = bank2stub.MockInit(uid, [][]byte{[]byte("Bank of Internet"), []byte("0002"), []byte("forex"), []byte("ibank")}) 63 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 64 | 65 | //create account on bank 1 66 | uid = uuid.New().String() 67 | stringArgs = []string{"createAccount", "Bob Jones", "1", "0", "USD"} 68 | 69 | response = bankStub.MockInvoke(uid, util.ArrayToChaincodeArgs(stringArgs)) 70 | 71 | //create account on bank 2 72 | uid = uuid.New().String() 73 | response = bank2stub.MockInvoke(uid, [][]byte{[]byte("createAccount"), 74 | []byte("Joe Blogs"), []byte("1234567"), []byte("1000"), []byte("USD")}) 75 | 76 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 77 | 78 | uid = uuid.New().String() 79 | response = bankStub.MockInvoke(uid, [][]byte{[]byte("queryAccount"), []byte("1")}) 80 | 81 | //register route to bank 82 | uid = uuid.New().String() 83 | response = ibankStub.MockInvoke(uid, [][]byte{[]byte("registerRoute"), []byte("0001"), []byte("bank"), []byte("forex")}) 84 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 85 | 86 | //perform transfer 87 | uid = uuid.New().String() 88 | 89 | stringArgs = []string{"transfer", "1234567", "0001", "1", "1000"} 90 | response = bank2stub.MockInvoke(uid, util.ArrayToChaincodeArgs(stringArgs)) 91 | assert.EqualValues(t, shim.OK, response.GetStatus(), response.Message) 92 | 93 | //Query To Account 94 | uid = uuid.New().String() 95 | 96 | response = bankStub.MockInvoke(uid, [][]byte{[]byte("queryAccount"), 97 | []byte("1")}) 98 | 99 | assert.EqualValues(t, shim.OK, response.GetStatus(), response.Message) 100 | 101 | resonseAccount := &account{} 102 | 103 | err := json.Unmarshal(response.GetPayload(), resonseAccount) 104 | if err != nil { 105 | panic(err) 106 | } 107 | 108 | validateBalance, _ := decimal.NewFromString("1000") 109 | assert.Equal(t, validateBalance, resonseAccount.Balance, "incorrect balance") 110 | } 111 | -------------------------------------------------------------------------------- /chaincode/src/forex/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "forex" 6 | "github.com/hyperledger/fabric/core/chaincode/shim" 7 | ) 8 | 9 | func main() { 10 | err := shim.Start(new(forex.ForexChaincode)) 11 | if err != nil { 12 | fmt.Printf("Error creating new ForexChaincode: %s", err) 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /chaincode/src/forex/forex.go: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | package forex 18 | 19 | import ( 20 | "encoding/json" 21 | "github.com/hyperledger/fabric/core/chaincode/shim" 22 | sc "github.com/hyperledger/fabric/protos/peer" 23 | "strconv" 24 | ) 25 | 26 | type forex struct { 27 | Pair string `json:"pair"` 28 | Rate float64 `json:"rate"` 29 | } 30 | 31 | //ForexChaincode is the struct that all chaincode methods are associated with 32 | type ForexChaincode struct { 33 | } 34 | 35 | //Init method is invokved on installation and upgrade 36 | func (s *ForexChaincode) Init(stub shim.ChaincodeStubInterface) sc.Response { 37 | return shim.Success(nil) 38 | } 39 | 40 | //Invoke is called when external applications invoke the smart contract 41 | func (s *ForexChaincode) Invoke(stub shim.ChaincodeStubInterface) sc.Response { 42 | function, args := stub.GetFunctionAndParameters() 43 | 44 | if function == "createUpdateForexPair" { 45 | return s.createUpdateForexPair(stub, args) 46 | } else if function == "getForexPair" { 47 | return s.getForexPair(stub, args) 48 | } 49 | 50 | return shim.Error("Invalid function") 51 | } 52 | 53 | func (s *ForexChaincode) createUpdateForexPair(stub shim.ChaincodeStubInterface, args []string) sc.Response { 54 | 55 | if len(args) != 3 { 56 | return shim.Error("Expecting 3 arguments, base currency, counter currency , rate") 57 | } 58 | 59 | baseCurrency := args[0] 60 | counterCurrency := args[1] 61 | rate, err := strconv.ParseFloat(args[2], 64) 62 | pair := baseCurrency + ":" + counterCurrency 63 | 64 | if err != nil { 65 | return shim.Error("Unable to parse rate from arg[2]") 66 | } 67 | 68 | forexPair := forex{Pair: pair, Rate: rate} 69 | asBytes, _ := json.Marshal(forexPair) 70 | err = stub.PutState(pair, asBytes) 71 | 72 | if err != nil { 73 | return shim.Error("Unable to commit pair to ledger") 74 | 75 | } 76 | 77 | return shim.Success(nil) 78 | } 79 | 80 | func (s *ForexChaincode) getForexPair(stub shim.ChaincodeStubInterface, args []string) sc.Response { 81 | if len(args) != 2 { 82 | return shim.Error("Incorrect number of arguments. Expecting the base currency and counter currency") 83 | } 84 | 85 | baseCurrency := args[0] 86 | counterCurrency := args[1] 87 | pair := baseCurrency + ":" + counterCurrency 88 | 89 | pairAsBytes, stubError := stub.GetState(pair) 90 | 91 | if stubError != nil { 92 | return shim.Error(stubError.Error()) 93 | 94 | } 95 | return (shim.Success(pairAsBytes)) 96 | } 97 | 98 | func main() { 99 | err := shim.Start(new(ForexChaincode)) 100 | if err != nil { 101 | panic("Error creating new ForexChaincode: " + err.Error()) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /chaincode/src/forex/forex_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | package forex 18 | 19 | import ( 20 | "encoding/json" 21 | "github.com/google/uuid" 22 | "github.com/hyperledger/fabric/common/util" 23 | "github.com/hyperledger/fabric/core/chaincode/shim" 24 | "github.com/stretchr/testify/assert" 25 | "strconv" 26 | "testing" 27 | ) 28 | 29 | func TestForex(t *testing.T) { 30 | stub := shim.NewMockStub("forex", new(ForexChaincode)) 31 | uuid := uuid.New().String() 32 | 33 | const RATE = "0.88" 34 | const BASECURRENCY = "USD" 35 | const COUNTERCURRENCY = "GBP" 36 | 37 | stringArgs := []string{"createUpdateForexPair", BASECURRENCY, COUNTERCURRENCY, RATE} 38 | args := util.ArrayToChaincodeArgs(stringArgs) 39 | 40 | response := stub.MockInvoke(uuid, args) 41 | 42 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 43 | 44 | stringArgs = []string{"getForexPair", BASECURRENCY, COUNTERCURRENCY} 45 | args = util.ArrayToChaincodeArgs(stringArgs) 46 | 47 | response = stub.MockInvoke(uuid, args) 48 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 49 | 50 | responseForex := &forex{} 51 | err := json.Unmarshal(response.GetPayload(), responseForex) 52 | 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | rateAsFloat, _ := strconv.ParseFloat(RATE, 64) 58 | 59 | assert.Equal(t, rateAsFloat, responseForex.Rate, "rate mismatch") 60 | 61 | } 62 | -------------------------------------------------------------------------------- /chaincode/src/interbank/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hyperledger/fabric/core/chaincode/shim" 6 | "interbank" 7 | ) 8 | 9 | func main() { 10 | err := shim.Start(new(interbank.InterbankChaincode)) 11 | if err != nil { 12 | fmt.Printf("Error creating new InterbankChaincode: %s", err) 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /chaincode/src/interbank/interbank.go: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | package interbank 18 | 19 | import ( 20 | "encoding/json" 21 | "errors" 22 | "fmt" 23 | "github.com/hyperledger/fabric/common/util" 24 | "github.com/hyperledger/fabric/core/chaincode/shim" 25 | sc "github.com/hyperledger/fabric/protos/peer" 26 | "github.com/shopspring/decimal" 27 | ) 28 | 29 | type route struct { 30 | ID string `json:"name"` 31 | BankContract string `json:"bankContract"` 32 | ForexContract string `json:"ForexContract"` 33 | } 34 | 35 | type account struct { 36 | Name string `json:"name"` 37 | AccNumber string `json:"ID"` 38 | Balance decimal.Decimal `json:"balance"` 39 | Currency string `json:"currency"` 40 | } 41 | 42 | type forexPair struct { 43 | Pair string `json:"pair"` 44 | Rate float64 `json:"rate"` 45 | } 46 | 47 | // InterbankChaincode is the struct to which all contract methods are associated with 48 | type InterbankChaincode struct { 49 | } 50 | 51 | // Initalize the chaincode 52 | func (s *InterbankChaincode) Init(stub shim.ChaincodeStubInterface) sc.Response { 53 | return shim.Success(nil) 54 | } 55 | 56 | //Invoke is called when external applications invoke the smart contract 57 | func (s *InterbankChaincode) Invoke(stub shim.ChaincodeStubInterface) sc.Response { 58 | function, args := stub.GetFunctionAndParameters() 59 | 60 | if function == "interbankTransfer" { 61 | return s.interbankTransfer(stub, args) 62 | } else if function == "registerRoute" { 63 | return s.registerRoute(stub, args) 64 | } 65 | 66 | return shim.Error("Invalid function") 67 | } 68 | 69 | // Perform a transfer between two banks 70 | // params: 71 | // toAccNumber string the account number to pay 72 | // toBankID string the ID of the bank that the account belongs to 73 | // amount string the amount to pay 74 | // currency string the currency of the amount being paid 75 | func (s *InterbankChaincode) interbankTransfer(stub shim.ChaincodeStubInterface, args []string) sc.Response { 76 | 77 | toAccNum := args[0] 78 | toBankID := args[1] 79 | amount := args[2] 80 | currency := args[3] 81 | 82 | routeAsBytes, err := stub.GetState(toBankID) 83 | 84 | if err != nil { 85 | return shim.Error(err.Error()) 86 | } 87 | 88 | toRoute := &route{} 89 | err = json.Unmarshal(routeAsBytes, toRoute) 90 | if err != nil { 91 | return shim.Error(err.Error()) 92 | } 93 | 94 | toBankContract := toRoute.BankContract 95 | 96 | stringArgs := []string{"queryAccount", toAccNum} 97 | response := stub.InvokeChaincode(toBankContract, util.ArrayToChaincodeArgs(stringArgs), "") 98 | toAccount := &account{} 99 | err = json.Unmarshal(response.Payload, toAccount) 100 | 101 | if err != nil { 102 | return shim.Error("Unable to retrieve to account from ledger " + err.Error()) 103 | } 104 | 105 | var exchangeRate decimal.Decimal 106 | 107 | //check if currency conversion is required 108 | if currency == toAccount.Currency { 109 | exchangeRate = decimal.NewFromFloat(1.0) 110 | } else { 111 | forexContract := toRoute.ForexContract 112 | exchangeRateAsFloat, err := currencyConversion(stub, forexContract, currency, toAccount.Currency) 113 | 114 | if err != nil { 115 | return shim.Error("Unable to perform currency conversion " + err.Error()) 116 | } 117 | 118 | exchangeRate = decimal.NewFromFloat(exchangeRateAsFloat) 119 | } 120 | 121 | //perform payment 122 | amountAsDecimal, err := decimal.NewFromString(amount) 123 | 124 | if err != nil { 125 | return shim.Error(err.Error()) 126 | } 127 | 128 | amountAsDecimal = amountAsDecimal.Mul(exchangeRate) 129 | amountAsString := amountAsDecimal.String() 130 | 131 | stringArgs = []string{"deposit", toAccNum, amountAsString} 132 | response = stub.InvokeChaincode(toBankContract, util.ArrayToChaincodeArgs(stringArgs), "") 133 | 134 | if response.GetStatus() != shim.OK { 135 | return shim.Error("Failed to make payment " + err.Error()) 136 | } 137 | 138 | return (shim.Success(nil)) 139 | } 140 | 141 | func currencyConversion(stub shim.ChaincodeStubInterface, forexContract string, baseCurrency string, counterCurrency string) (float64, error) { 142 | 143 | // invoke the forex contract to get the exchange rate for the pair 144 | stringArgs := []string{"getForexPair", baseCurrency, counterCurrency} 145 | args := util.ArrayToChaincodeArgs(stringArgs) 146 | response := stub.InvokeChaincode(forexContract, args, "") 147 | 148 | if response.Status != shim.OK { 149 | return 0.0, errors.New("Unable to get exchange rate from Forex Contract" + response.Message) 150 | } 151 | 152 | responseForex := &forexPair{} 153 | err := json.Unmarshal(response.GetPayload(), responseForex) 154 | 155 | if err != nil { 156 | return 0.0, errors.New("Unable to unmarshal exchange rate from Forex Contract" + err.Error()) 157 | } 158 | 159 | return responseForex.Rate, nil 160 | } 161 | 162 | //registerRoute registers the contracts associated with a bank to allow for interbank transfer 163 | //Args 164 | // ID string the ID of the bank, analogue of SWIFT, IBAN, routing code, etc 165 | // BankContract string the name of the chaincode for the bank 166 | // ForexContract string the name of the contract to provide forex services 167 | func (s *InterbankChaincode) registerRoute(stub shim.ChaincodeStubInterface, args []string) sc.Response { 168 | if len(args) != 3 { 169 | return shim.Error("Expecting 3 arguments: bank ID, name of BankChaincode, name of ForexContract") 170 | } 171 | 172 | newRoute := route{ID: args[0], BankContract: args[1], ForexContract: args[2]} 173 | asBytes, _ := json.Marshal(newRoute) 174 | err := stub.PutState(args[0], asBytes) 175 | 176 | if err != nil { 177 | return shim.Error("Unable to commit route to ledger") 178 | } 179 | 180 | return shim.Success(nil) 181 | } 182 | 183 | func main() { 184 | err := shim.Start(new(InterbankChaincode)) 185 | if err != nil { 186 | fmt.Printf("Error creating new InterbankChaincodet: %s", err) 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /chaincode/src/interbank/interbank_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | package interbank 18 | 19 | import ( 20 | "bank" 21 | "encoding/json" 22 | "forex" 23 | "github.com/google/uuid" 24 | "github.com/hyperledger/fabric/common/util" 25 | 26 | "github.com/hyperledger/fabric/core/chaincode/shim" 27 | "github.com/shopspring/decimal" 28 | "github.com/stretchr/testify/assert" 29 | "testing" 30 | ) 31 | 32 | func TestTransfer(t *testing.T) { 33 | 34 | fx := new(forex.ForexChaincode) 35 | b1 := new(bank.BankChaincode) 36 | ibank := new(InterbankChaincode) 37 | 38 | forexStub := shim.NewMockStub("forex", fx) 39 | bankStub := shim.NewMockStub("bank", b1) 40 | ibankStub := shim.NewMockStub("ibank", ibank) 41 | 42 | uid := uuid.New().String() 43 | response := forexStub.MockInit(uid, [][]byte{}) 44 | 45 | uid = uuid.New().String() 46 | response = ibankStub.MockInit(uid, [][]byte{}) 47 | 48 | ibankStub.MockPeerChaincode("forex", forexStub) 49 | ibankStub.MockPeerChaincode("bank", bankStub) 50 | 51 | //create forexpair 52 | uid = uuid.New().String() 53 | stringArgs := []string{"createUpdateForexPair", "GBP", "USD", "1.20"} 54 | args := util.ArrayToChaincodeArgs(stringArgs) 55 | response = forexStub.MockInvoke(uid, args) 56 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 57 | 58 | //create forexpair 59 | uid = uuid.New().String() 60 | stringArgs = []string{"createUpdateForexPair", "USD", "GBP", "0.80"} 61 | args = util.ArrayToChaincodeArgs(stringArgs) 62 | response = forexStub.MockInvoke(uid, args) 63 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 64 | 65 | //create bank 66 | uid = uuid.New().String() 67 | response = bankStub.MockInit(uid, [][]byte{[]byte("CloudBank"), []byte("0001"), []byte("forex")}) 68 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 69 | 70 | //create account 71 | uid = uuid.New().String() 72 | response = bankStub.MockInvoke(uid, [][]byte{[]byte("createAccount"), 73 | []byte("Bob Jones"), []byte("1"), []byte("0"), []byte("USD")}) 74 | 75 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 76 | 77 | uid = uuid.New().String() 78 | response = bankStub.MockInvoke(uid, [][]byte{[]byte("queryAccount"), []byte("1")}) 79 | 80 | //register route to bank 81 | uid = uuid.New().String() 82 | response = ibankStub.MockInvoke(uid, [][]byte{[]byte("registerRoute"), []byte("0001"), []byte("bank"), []byte("forex")}) 83 | assert.EqualValues(t, shim.OK, response.GetStatus(), "failed to execute invocation") 84 | 85 | //perform transfer 86 | uid = uuid.New().String() 87 | 88 | stringArgs = []string{"interbankTransfer", "1", "0001", "100", "GBP"} 89 | response = ibankStub.MockInvoke(uid, util.ArrayToChaincodeArgs(stringArgs)) 90 | assert.EqualValues(t, shim.OK, response.GetStatus(), response.Message) 91 | 92 | //Query To Account 93 | uid = uuid.New().String() 94 | 95 | response = bankStub.MockInvoke(uid, [][]byte{[]byte("queryAccount"), 96 | []byte("1")}) 97 | 98 | assert.EqualValues(t, shim.OK, response.GetStatus(), response.Message) 99 | 100 | resonseAccount := &account{} 101 | 102 | err := json.Unmarshal(response.GetPayload(), resonseAccount) 103 | if err != nil { 104 | panic(err) 105 | } 106 | 107 | validateBalance, _ := decimal.NewFromString("120") 108 | assert.Equal(t, validateBalance, resonseAccount.Balance, "incorrect balance") 109 | } 110 | -------------------------------------------------------------------------------- /events/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "localhost", 3 | "port": "3000", 4 | "channelName": "bar", 5 | "chaincodeName": "globalbank", 6 | "eventWaitTime": "30000", 7 | "clientConfig": "../tmp/connection-profile/org1/client-org.yaml", 8 | "org": "org1", 9 | "peers": [ 10 | "peer1" 11 | ], 12 | "admins": [{ 13 | "username": "admin", 14 | "secret": "Admin123" 15 | }] 16 | } 17 | -------------------------------------------------------------------------------- /events/connection.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | 'use strict'; 18 | var log4js = require('log4js'); 19 | var logger = log4js.getLogger('Connection'); 20 | var util = require('util'); 21 | var hfc = require('fabric-client'); 22 | hfc.setLogger(logger); 23 | 24 | var getLogger = function(moduleName) { 25 | var logger = log4js.getLogger(moduleName); 26 | return logger; 27 | }; 28 | 29 | async function getClientForOrg (userorg, username) { 30 | logger.info('============ START getClientForOrg for org %s and user %s', userorg, username); 31 | let config = '../tmp/connection-profile/bank-connection-profile.yaml'; 32 | let clientConfig = '../tmp/connection-profile/' + userorg + '/client-' + userorg + '.yaml'; 33 | 34 | logger.info('##### getClient - Loading connection profiles from file: %s and %s', config, clientConfig); 35 | 36 | // Load the connection profiles. First load the network settings, then load the client specific settings 37 | let client = hfc.loadFromConfig(config); 38 | client.loadFromConfig(clientConfig); 39 | 40 | // Create the state store and the crypto store 41 | await client.initCredentialStores(); 42 | 43 | // Try and obtain the user from persistence if the user has previously been 44 | // registered and enrolled 45 | if(username) { 46 | let user = await client.getUserContext(username, true); 47 | if(!user) { 48 | throw new Error(util.format('##### getClient - User was not found :', username)); 49 | } else { 50 | logger.info('##### getClient - User %s was found to be registered and enrolled', username); 51 | } 52 | } 53 | logger.info('============ END getClientForOrg for org %s and user %s \n\n', userorg, username); 54 | 55 | return client; 56 | } 57 | 58 | var getRegisteredUser = async function(username, userorg, isJson) { 59 | try { 60 | logger.info('============ START getRegisteredUser - for org %s and user %s', userorg, username); 61 | var client = await getClientForOrg(userorg); 62 | var user = await client.getUserContext(username, true); 63 | if (user && user.isEnrolled()) { 64 | logger.info('##### getRegisteredUser - User %s already enrolled', username); 65 | } else { 66 | // user was not enrolled, so we will need an admin user object to register 67 | logger.info('##### getRegisteredUser - User %s was not enrolled, so we will need an admin user object to register', username); 68 | logger.info('##### getRegisteredUser - Got hfc %s', util.inspect(hfc)); 69 | var admins = hfc.getConfigSetting('admins'); 70 | logger.info('##### getRegisteredUser - Got admin property %s', util.inspect(admins)); 71 | let adminUserObj = await client.setUserContext({username: admins[0].username, password: admins[0].secret}); 72 | logger.info('##### getRegisteredUser - Got adminUserObj property %s', util.inspect(admins)); 73 | let caClient = client.getCertificateAuthority(); 74 | logger.info('##### getRegisteredUser - Got caClient %s', util.inspect(admins)); 75 | let secret = await caClient.register({ 76 | enrollmentID: username 77 | }, adminUserObj); 78 | logger.info('##### getRegisteredUser - Successfully got the secret for user %s', username); 79 | user = await client.setUserContext({username:username, password:secret}); 80 | logger.info('##### getRegisteredUser - Successfully enrolled username %s and setUserContext on the client object', username); 81 | } 82 | if(user && user.isEnrolled) { 83 | if (isJson && isJson === true) { 84 | var response = { 85 | success: true, 86 | secret: user._enrollmentSecret, 87 | message: username + ' enrolled Successfully', 88 | }; 89 | return response; 90 | } 91 | } 92 | else { 93 | throw new Error('##### getRegisteredUser - User was not enrolled '); 94 | } 95 | } 96 | catch(error) { 97 | logger.error('##### getRegisteredUser - Failed to get registered user: %s with error: %s', username, error.toString()); 98 | return 'failed '+error.toString(); 99 | } 100 | }; 101 | 102 | 103 | exports.getRegisteredUser = getRegisteredUser; 104 | exports.getClientForOrg = getClientForOrg; 105 | exports.getLogger = getLogger; 106 | -------------------------------------------------------------------------------- /events/invoke.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TRANSFER_URL=http://$(curl http://169.254.169.254/latest/meta-data/public-ipv4):8081/transfer 4 | FROM_ACCT=0000001 5 | FROM_BANK=0001 6 | TO_ACCT=101010 7 | TO_BANK=0005 8 | while [ 1 ]; do 9 | TRANSFER=$(($RANDOM % 10)) 10 | 11 | echo "Transfer 0.0$TRANSFER from account $FROM_ACCT to $TO_ACCT at $TO_BANK" 12 | curl ${TRANSFER_URL} \ 13 | --data-binary "{\"FromAccNumber\":\"$FROM_ACCT\",\"ToBankID\":\"$TO_BANK\",\"ToAccNumber\":\"$TO_ACCT\",\"Amount\":0.0$TRANSFER}" \ 14 | -H 'Content-Type: application/json' 15 | echo 16 | sleep 1 17 | done 18 | -------------------------------------------------------------------------------- /events/listener.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | # 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const kinesis_stream = 'bank-transfer-events' 20 | 21 | const log4js = require('log4js'); 22 | const util = require('util'); 23 | const hfc = require('fabric-client'); 24 | const aws = require('aws-sdk'); 25 | const config = require('./config.json'); 26 | 27 | 28 | log4js.configure({ 29 | appenders: { 30 | out: { type: 'stdout' }, 31 | }, 32 | categories: { 33 | default: { appenders: ['out'], level: 'info' }, 34 | } 35 | }); 36 | 37 | const kinesis = new aws.Kinesis({ 38 | apiVersion: '2013-12-02', 39 | region: 'us-east-1' 40 | }); 41 | 42 | var logger = log4js.getLogger('CHAINCODE-EVENT-LISTENER'); 43 | 44 | 45 | hfc.addConfigFile('config.json'); 46 | 47 | var username = 'admin'; 48 | var orgName = config.org 49 | var eventName = "transfer-event"; 50 | var channelName = hfc.getConfigSetting('channelName'); 51 | var chaincodeName = hfc.getConfigSetting('chaincodeName'); 52 | var peers = hfc.getConfigSetting('peers'); 53 | const connection = require('./connection.js'); 54 | 55 | 56 | async function main() { 57 | logger.info('============ Starting Listening for Events'); 58 | 59 | await connection.getRegisteredUser(username, orgName, true); 60 | const fabric_client = await connection.getClientForOrg(orgName, username); 61 | const channel = fabric_client.getChannel() 62 | const eventHub = channel.getChannelEventHubsForOrg()[0]; 63 | eventHub.connect(true); 64 | logger.info('Listening for %s on %s using org %s', eventName, channel, orgName); 65 | 66 | eventHub.registerChaincodeEvent(chaincodeName, eventName, 67 | (event, block_num, txnid, status) => { 68 | console.log(event); 69 | 70 | var record = JSON.parse(event['payload']) 71 | console.log(record) 72 | 73 | var params = { 74 | Data: JSON.stringify(record) + "\n", 75 | PartitionKey: record['ToBankID'], 76 | StreamName: kinesis_stream 77 | }; 78 | 79 | kinesis.putRecord(params, function(err, data) { 80 | if (err) console.log(err, err.stack); // an error occurred 81 | else console.log(data); // successful response 82 | }); 83 | 84 | 85 | }, 86 | (error) => { 87 | console.log('Failed to receive the chaincode event ::' + error); 88 | } 89 | ); 90 | } 91 | 92 | main(); 93 | -------------------------------------------------------------------------------- /events/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngo-rest-api", 3 | "version": "1.0.0", 4 | "description": "A listener for chaincode events", 5 | "main": "listener.js", 6 | "scripts": { 7 | "start": "node listener.js" 8 | }, 9 | "engines": { 10 | "node": ">=8.9.4 <9.0", 11 | "npm": ">=5.6.0 <6.0" 12 | }, 13 | "license": "Apache-2.0", 14 | "dependencies": { 15 | "aws-sdk": "^2.580.0", 16 | "body-parse": "^0.1.0", 17 | "body-parser": "^1.19.0", 18 | "cookie-parser": "^1.4.4", 19 | "cors": "^2.8.5", 20 | "express": "^4.17.1", 21 | "fabric-ca-client": "^1.4.4", 22 | "fabric-client": "^1.4.4", 23 | "log4js": "^6.1.0", 24 | "uuid": "^3.3.3", 25 | "ws": "^7.2.0" 26 | }, 27 | "devDependencies": {}, 28 | "author": "" 29 | } -------------------------------------------------------------------------------- /images/analytics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/bank-transfer-blockchain-reinvent2019-workshop/aea7adeafe322f2f31ab73dd35eb22a1a334c3b3/images/analytics.png -------------------------------------------------------------------------------- /images/endtoend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/bank-transfer-blockchain-reinvent2019-workshop/aea7adeafe322f2f31ab73dd35eb22a1a334c3b3/images/endtoend.png -------------------------------------------------------------------------------- /images/interbank-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/bank-transfer-blockchain-reinvent2019-workshop/aea7adeafe322f2f31ab73dd35eb22a1a334c3b3/images/interbank-arch.png -------------------------------------------------------------------------------- /images/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/bank-transfer-blockchain-reinvent2019-workshop/aea7adeafe322f2f31ab73dd35eb22a1a334c3b3/images/overview.png -------------------------------------------------------------------------------- /images/quicksight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/bank-transfer-blockchain-reinvent2019-workshop/aea7adeafe322f2f31ab73dd35eb22a1a334c3b3/images/quicksight.png -------------------------------------------------------------------------------- /images/register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/bank-transfer-blockchain-reinvent2019-workshop/aea7adeafe322f2f31ab73dd35eb22a1a334c3b3/images/register.png -------------------------------------------------------------------------------- /setup/cloudformation/team.yaml: -------------------------------------------------------------------------------- 1 | Description: "A template that creates a Cloud9 Environment" 2 | 3 | Resources: 4 | Cloud9Env: 5 | Type: "AWS::Cloud9::EnvironmentEC2" 6 | Properties: 7 | Name: AthenaWorkshopEnvironment 8 | Description: "Cloud9 environment for Athena Federation Workshop" 9 | InstanceType: "t3.large" 10 | AutomaticStopTimeMinutes: 1440 11 | 12 | AdminPassword: 13 | Type: "AWS::SecretsManager::Secret" 14 | Properties: 15 | Name: AmazonBlockchainWorkshopAdminPassword 16 | Description: "The Admin Password for our Blockchain Network" 17 | SecretString: Admin123 18 | #Generate a random password. 19 | #GenerateSecretString: 20 | # ExcludeLowercase: false 21 | # ExcludeNumbers: false 22 | # ExcludePunctuation: True 23 | # ExcludeUppercase: false 24 | # IncludeSpace: false 25 | # PasswordLength: 10 26 | 27 | Outputs: 28 | Cloud9EnvArn: 29 | Description: "The Cloud9 Environment ARN" 30 | Value: !Ref Cloud9Env -------------------------------------------------------------------------------- /setup/config: -------------------------------------------------------------------------------- 1 | # -*-perl-*- 2 | 3 | package.BanktransferBlockchainWorkshop = { 4 | interfaces = (1.0); 5 | 6 | deploy = { 7 | generic = true; 8 | }; 9 | 10 | build-environment = { 11 | chroot = basic; 12 | network-access = blocked; 13 | }; 14 | 15 | # Use NoOpBuild. See https://w.amazon.com/index.php/BrazilBuildSystem/NoOpBuild 16 | build-system = no-op; 17 | build-tools = { 18 | 1.0 = { 19 | NoOpBuild = 1.0; 20 | }; 21 | }; 22 | 23 | # Use runtime-dependencies for when you want to bring in additional 24 | # packages when deploying. 25 | # Use dependencies instead if you intend for these dependencies to 26 | # be exported to other packages that build against you. 27 | dependencies = { 28 | 1.0 = { 29 | }; 30 | }; 31 | 32 | runtime-dependencies = { 33 | 1.0 = { 34 | }; 35 | }; 36 | 37 | }; 38 | -------------------------------------------------------------------------------- /setup/configtx.yaml: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # 3 | # Section: Organizations 4 | # 5 | # - This section defines the different organizational identities which will 6 | # be referenced later in the configuration. 7 | # 8 | ################################################################################ 9 | Organizations: 10 | - &Org1 11 | # member id defines the organization 12 | Name: 13 | # ID to load the MSP definition as 14 | ID: 15 | #msp dir of org1 in the docker container 16 | MSPDir: /opt/home/admin-msp 17 | # AnchorPeers defines the location of peers which can be used 18 | # for cross org gossip communication. Note, this value is only 19 | # encoded in the genesis block in the Application section context 20 | AnchorPeers: 21 | - Host: 22 | Port: 23 | - &Org2 24 | Name: 25 | ID: 26 | MSPDir: /opt/home/ 27 | AnchorPeers: 28 | - Host: 29 | Port: 30 | - &Org3 31 | Name: 32 | ID: 33 | MSPDir: /opt/home/ 34 | AnchorPeers: 35 | - Host: 36 | Port: 37 | - &Org4 38 | Name: 39 | ID: 40 | MSPDir: /opt/home/ 41 | AnchorPeers: 42 | - Host: 43 | Port: 44 | - &Org5 45 | Name: 46 | ID: 47 | MSPDir: /opt/home/ 48 | AnchorPeers: 49 | - Host: 50 | Port: 51 | ################################################################################ 52 | # 53 | # SECTION: Application 54 | # 55 | # - This section defines the values to encode into a config transaction or 56 | # genesis block for application related parameters 57 | # 58 | ################################################################################ 59 | Application: &ApplicationDefaults 60 | # Organizations is the list of orgs which are defined as participants on 61 | # the application side of the network 62 | Organizations: 63 | 64 | ################################################################################ 65 | # 66 | # Profile 67 | # 68 | # - Different configuration profiles may be encoded here to be specified 69 | # as parameters to the configtxgen tool 70 | # 71 | ################################################################################ 72 | Profiles: 73 | FiveOrgChannel: 74 | Consortium: AWSSystemConsortium 75 | Application: 76 | <<: *ApplicationDefaults 77 | Organizations: 78 | - *Org1 79 | - *Org2 80 | - *Org3 81 | - *Org4 82 | - *Org5 83 | -------------------------------------------------------------------------------- /setup/copy_public_certificates.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | 15 | import boto3; 16 | import sys, re, optparse 17 | import copy, datetime, time 18 | import requests 19 | import random 20 | import string 21 | import os 22 | from os.path import expanduser 23 | import subprocess 24 | import shutil 25 | 26 | assert sys.version_info > (3,0) 27 | 28 | ambClient = boto3.client('managedblockchain') 29 | s3Client = boto3.client("s3") 30 | 31 | #Constants 32 | certificateBucket = "reinvent2019-amb-artifacts-us-east-1" 33 | 34 | #get all members from the network. 35 | networkid = os.getenv("NETWORKID", "") 36 | memberid = os.getenv("MEMBERID", "") 37 | 38 | if len(networkid) == 0 or len(memberid) == 0: 39 | print("NETWORKID or MEMBERID environment variables are not set! Please run 'source ~/fabric_exports'") 40 | sys.exit() 41 | 42 | blockchainMembers = ambClient.list_members(Status='AVAILABLE', NetworkId=networkid, IsOwned=False) 43 | 44 | membersFound=[] 45 | 46 | print("We are using Blockchain Network Id: " + networkid + " and Member: " + memberid) 47 | print("Please ensure that all other members in your network have run setup_fabric_environment before running this script! ") 48 | input("Press enter to continue...") 49 | 50 | for member in blockchainMembers['Members']: 51 | #No need to copy my own certificate 52 | if member['Id'] == memberid: 53 | continue 54 | if "Ignore" in member['Name'] or member['Name'] == "AmazonManagedBlockchainMember": 55 | print ("Ignoring Member: " + member['Name'] + " because it is not a part of this workshop.") 56 | continue 57 | 58 | #Get the Admin Public Cert for the member 59 | adminPublicCert = None 60 | try: 61 | adminPublicCerts3Key = networkid + "/" + member['Id'] + "/admin-msp/admincerts/cert.pem" 62 | adminPublicCert = s3Client.get_object(Bucket=certificateBucket, Key=adminPublicCerts3Key) 63 | except: 64 | print("Failed to find certificates for Member: " + member['Name'] + "(" + member['Id'] + ")") 65 | continue 66 | 67 | #Get the CA Public Cert for the member 68 | caPublicCert = None 69 | try: 70 | caPublicCerts3Key = networkid + "/" + member['Id'] + "/admin-msp/cacerts/ca-" + member['Id'] + "-" + networkid + "-us-east-1-amazonaws-com.pem" 71 | caPublicCert = s3Client.get_object(Bucket=certificateBucket, Key=caPublicCerts3Key) 72 | except: 73 | print("Failed to find certificates for Member: " + member['Name'] + "(" + member['Id'] + ")") 74 | continue 75 | 76 | #Delete any old directories. 77 | shutil.rmtree(expanduser("~") + "/" + member['Id'] + "-msp/", ignore_errors=True) 78 | 79 | #Create any directors. 80 | os.mkdir(expanduser("~") + "/" + member['Id'] + "-msp/") 81 | os.mkdir(expanduser("~") + "/" + member['Id'] + "-msp/admincerts") 82 | os.mkdir(expanduser("~") + "/" + member['Id'] + "-msp/cacerts") 83 | 84 | #Download the certificates 85 | s3Client.download_file(Bucket=certificateBucket, Key=adminPublicCerts3Key, Filename=expanduser("~") + "/" + member['Id'] + "-msp/admincerts/cert.pem") 86 | s3Client.download_file(Bucket=certificateBucket, Key=caPublicCerts3Key, Filename=expanduser("~") + "/" + member['Id'] + "-msp/cacerts/cacert.pem") 87 | membersFound.append(member['Name'] + ":" + member['Id']) 88 | 89 | print("Copying members peer address. ") 90 | s3Client.download_file(Bucket=certificateBucket, 91 | Key=networkid + "/" + member['Id'] + "/peer_address.txt", 92 | Filename=expanduser("~") + "/environment/peer-address-" + member['Id'] + ".txt") 93 | 94 | if len(membersFound) == 0: 95 | print ("Did not find any members certificates! Please ensure that other participants have copied their member certificates from running setup_fabric_environment.") 96 | else: 97 | print ("Found the following members: " + '\n'.join(membersFound)) 98 | print ("Copied their certificates. If you are missing any certificates, ask other participants to setup their environment and run this script again.") 99 | 100 | -------------------------------------------------------------------------------- /setup/docker-compose-cli.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). 4 | # You may not use this file except in compliance with the License. 5 | # A copy of the License is located at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | version: '2' 15 | services: 16 | cli: 17 | container_name: cli 18 | image: hyperledger/fabric-tools:1.2.0 19 | tty: true 20 | environment: 21 | - GOPATH=/opt/gopath 22 | - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock 23 | - CORE_LOGGING_LEVEL=info # Set logging level to debug for more verbose logging 24 | - CORE_PEER_ID=cli 25 | - CORE_CHAINCODE_KEEPALIVE=10 26 | working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer 27 | command: /bin/bash 28 | volumes: 29 | - /var/run/:/host/var/run/ 30 | - /home/ec2-user/fabric-samples/chaincode:/opt/gopath/src/github.com/ 31 | - /home/ec2-user:/opt/home -------------------------------------------------------------------------------- /setup/private-configtx.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). 4 | # You may not use this file except in compliance with the License. 5 | # A copy of the License is located at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # or in the "license" file accompanying this file. This file is distributed 10 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | # express or implied. See the License for the specific language governing 12 | # permissions and limitations under the License. 13 | 14 | ################################################################################ 15 | # 16 | # Section: Organizations 17 | # 18 | # - This section defines the different organizational identities which will 19 | # be referenced later in the configuration. 20 | # 21 | ################################################################################ 22 | Organizations: 23 | - &Org1 24 | Name: __MEMBERID__ 25 | 26 | # ID to load the MSP definition as 27 | ID: __MEMBERID__ 28 | 29 | MSPDir: /opt/home/admin-msp 30 | 31 | AnchorPeers: 32 | # AnchorPeers defines the location of peers which can be used 33 | # for cross org gossip communication. Note, this value is only 34 | # encoded in the genesis block in the Application section context 35 | - Host: 36 | Port: 37 | 38 | ################################################################################ 39 | # 40 | # SECTION: Application 41 | # 42 | # - This section defines the values to encode into a config transaction or 43 | # genesis block for application related parameters 44 | # 45 | ################################################################################ 46 | Application: &ApplicationDefaults 47 | 48 | # Organizations is the list of orgs which are defined as participants on 49 | # the application side of the network 50 | Organizations: 51 | 52 | ################################################################################ 53 | # 54 | # Profile 55 | # 56 | # - Different configuration profiles may be encoded here to be specified 57 | # as parameters to the configtxgen tool 58 | # 59 | ################################################################################ 60 | Profiles: 61 | 62 | OneOrgChannel: 63 | Consortium: AWSSystemConsortium 64 | Application: 65 | <<: *ApplicationDefaults 66 | Organizations: 67 | - *Org1 -------------------------------------------------------------------------------- /setup/s3-handler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"). 6 | # You may not use this file except in compliance with the License. 7 | # A copy of the License is located at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed 12 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 13 | # express or implied. See the License for the specific language governing 14 | # permissions and limitations under the License. 15 | 16 | set +e 17 | 18 | region=us-east-1 19 | thisMemberID=$MEMBERID 20 | 21 | # a whitespace seperated list of the other members on the network 22 | # this is needed only if you are the participant creating the channel 23 | # e.g: 24 | # memberList = "m-123 m-456 m=789" 25 | # replace with your list of members 26 | memberList="m-123 m-456 m-0789" 27 | 28 | # convert memberID to lowercase. S3 buckets must be lower case 29 | thisMemberID=$(echo "$thisMemberID" | tr '[:upper:]' '[:lower:]') 30 | S3BucketNameCreator=${thisMemberID}-creator 31 | S3BucketNameNewMember=${thisMemberID}-newmember 32 | 33 | 34 | # copy the certificates for the new Fabric member to S3 35 | function copyCertsToS3 { 36 | echo "Copying the certs for the new org to S3" 37 | if [[ $(aws configure list) && $? -eq 0 ]]; then 38 | aws s3api put-object --bucket $S3BucketNameNewMember --key ${thisMemberID}/admincerts --body /home/ec2-user/admin-msp/admincerts/cert.pem 39 | aws s3api put-object --bucket $S3BucketNameNewMember --key ${thisMemberID}/cacerts --body /home/ec2-user/admin-msp/cacerts/*.pem 40 | aws s3api put-object-acl --bucket $S3BucketNameNewMember --key ${thisMemberID}/admincerts --acl public-read 41 | aws s3api put-object-acl --bucket $S3BucketNameNewMember --key ${thisMemberID}/cacerts --acl public-read 42 | echo $member 43 | else 44 | echo "AWS CLI is not configured on this node. To run this script install and configure the AWS CLI" 45 | fi 46 | echo "Copying the certs for the new org to S3 complete" 47 | 48 | } 49 | 50 | # copy the certificates for the new Fabric member from S3 to the Fabric creator network 51 | function copyCertsFromS3 { 52 | echo "Copying the certs from S3" 53 | 54 | for member in $memberList; do 55 | member=$(echo "$member" | tr '[:upper:]' '[:lower:]') 56 | if [[ $(aws configure list) && $? -eq 0 ]]; then 57 | mkdir -p /home/ec2-user/${member}-msp/admincerts 58 | mkdir -p /home/ec2-user/${member}-msp/cacerts 59 | aws s3api get-object --bucket ${member}-newmember --key ${member}/admincerts /home/ec2-user/${member}-msp/admincerts/cert.pem 60 | aws s3api get-object --bucket ${member}-newmember --key ${member}/cacerts /home/ec2-user/${member}-msp/cacerts/cacert.pem 61 | ls -lR /home/ec2-user/${member}-msp/ 62 | echo $member 63 | else 64 | echo "AWS CLI is not configured on this node. To run this script install and configure the AWS CLI" 65 | fi 66 | done 67 | echo "Copying the certs from S3 complete" 68 | } 69 | # copy the Channel Genesis block from the Fabric creator network to S3 70 | function copyChannelGenesisToS3 { 71 | echo "Copying the Channel Genesis block to S3" 72 | if [[ $(aws configure list) && $? -eq 0 ]]; then 73 | aws s3api put-object --bucket $S3BucketNameCreator --key org0/mychannel.block --body /home/ec2-user/fabric-samples/chaincode/hyperledger/fabric/peer/mychannel.block 74 | aws s3api put-object-acl --bucket $S3BucketNameCreator --key org0/mychannel.block --grant-read uri=http://acs.amazonaws.com/groups/global/AllUsers 75 | aws s3api put-object-acl --bucket $S3BucketNameCreator --key org0/mychannel.block --acl public-read 76 | else 77 | echo "AWS CLI is not configured on this node. To run this script install and configure the AWS CLI" 78 | fi 79 | echo "Copying the Channel Genesis block to S3 complete" 80 | } 81 | 82 | # copy the Channel Genesis block from S3 to the new Fabric member 83 | function copyChannelGenesisFromS3 { 84 | echo "Copying the Channel Genesis block from S3" 85 | if [[ $(aws configure list) && $? -eq 0 ]]; then 86 | sudo chown -R ec2-user /home/ec2-user/fabric-samples/chaincode/hyperledger/fabric/peer 87 | aws s3api get-object --bucket $S3BucketNameCreator --key org0/mychannel.block /home/ec2-user/fabric-samples/chaincode/hyperledger/fabric/peer/mychannel.block 88 | else 89 | echo "AWS CLI is not configured on this node. To run this script install and configure the AWS CLI" 90 | fi 91 | echo "Copying the Channel Genesis block from S3 complete" 92 | } 93 | 94 | # create S3 bucket to copy files from the Fabric network creator. Bucket will be read-only to other members 95 | function createS3BucketForCreator { 96 | #create the s3 bucket 97 | echo -e "creating s3 bucket for network creator: $S3BucketNameCreator" 98 | #quick way of determining whether the AWS CLI is installed and a default profile exists 99 | if [[ $(aws configure list) && $? -eq 0 ]]; then 100 | if [[ "$region" == "us-east-1" ]]; then 101 | aws s3api create-bucket --bucket $S3BucketNameCreator --region $region 102 | else 103 | aws s3api create-bucket --bucket $S3BucketNameCreator --region $region --create-bucket-configuration LocationConstraint=$region 104 | fi 105 | aws s3api put-bucket-acl --bucket $S3BucketNameCreator --grant-read uri=http://acs.amazonaws.com/groups/global/AllUsers 106 | aws s3api put-bucket-acl --bucket $S3BucketNameCreator --acl public-read 107 | else 108 | echo "AWS CLI is not configured on this node. To run this script install and configure the AWS CLI" 109 | fi 110 | echo "Creating the S3 bucket complete" 111 | } 112 | 113 | # create S3 bucket to copy files from the new member. Bucket will be read-only to other members 114 | function createS3BucketForNewMember { 115 | #create the s3 bucket 116 | echo -e "creating s3 bucket for new member $NEW_ORG: $S3BucketNameNewMember" 117 | #quick way of determining whether the AWS CLI is installed and a default profile exists 118 | if [[ $(aws configure list) && $? -eq 0 ]]; then 119 | if [[ "$region" == "us-east-1" ]]; then 120 | aws s3api create-bucket --bucket $S3BucketNameNewMember --region $region 121 | else 122 | aws s3api create-bucket --bucket $S3BucketNameNewMember --region $region --create-bucket-configuration LocationConstraint=$region 123 | fi 124 | aws s3api put-bucket-acl --bucket $S3BucketNameNewMember --grant-read uri=http://acs.amazonaws.com/groups/global/AllUsers 125 | aws s3api put-bucket-acl --bucket $S3BucketNameNewMember --acl public-read 126 | else 127 | echo "AWS CLI is not configured on this node. To run this script install and configure the AWS CLI" 128 | fi 129 | echo "Creating the S3 bucket complete" 130 | } 131 | 132 | # This is a little hack I found here: https://stackoverflow.com/questions/8818119/how-can-i-run-a-function-from-a-script-in-command-line 133 | # that allows me to call this bash script and invoke a specific function from the command line 134 | "$@" 135 | 136 | 137 | -------------------------------------------------------------------------------- /setup/setup_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | 15 | echo Pre-requisities 16 | echo installing jq 17 | sudo yum -y install jq 18 | 19 | echo Updating AWS CLI to the latest version 20 | sudo pip install awscli --upgrade 21 | 22 | echo "Installing needed python libraries" 23 | sudo alternatives --set python /usr/bin/python3.6 24 | sudo pip install boto3 25 | sudo pip install requests 26 | 27 | echo "Updating YUM repos" 28 | sudo yum update -y 29 | 30 | echo "Installing telnet and emacs" 31 | sudo yum install -y telnet 32 | sudo yum -y install emacs 33 | 34 | echo "Updating Docker" 35 | sudo yum update -y docker 36 | 37 | echo "Installing GIT and other tools" 38 | sudo yum install libtool-ltdl-devel -y 39 | sudo yum install git -y 40 | 41 | echo "Installing Docker Compose" 42 | sudo curl -L https://github.com/docker/compose/releases/download/1.20.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose 43 | sudo chmod a+x /usr/local/bin/docker-compose 44 | sudo yum install libtool -y 45 | 46 | echo "Fixing Bash Profile" 47 | rm ~/.bash_profile 48 | cat > ~/.bash_profile << EOF 49 | # .bash_profile 50 | # Get the aliases and functions 51 | if [ -f ~/.bashrc ]; then 52 | . ~/.bashrc 53 | fi 54 | # User specific environment and startup programs 55 | PATH=$PATH:$HOME/.local/bin:$HOME/bin 56 | # GOROOT is the location where Go package is installed on your system 57 | export GOROOT=/usr/lib/golang/ 58 | # GOPATH is the location of your work directory 59 | export GOPATH=$HOME/go 60 | # PATH in order to access go binary system wide 61 | export PATH=$GOROOT/bin:$PATH 62 | EOF 63 | 64 | source ~/.bash_profile 65 | 66 | echo "Checking versions" 67 | docker version 68 | sudo /usr/local/bin/docker-compose version 69 | go version 70 | 71 | ## Setup Fabric client 72 | echo "Setting up Fabric Client" 73 | go get -u github.com/hyperledger/fabric-ca/cmd/... 74 | cd /home/ec2-user/go/src/github.com/hyperledger/fabric-ca 75 | make fabric-ca-client 76 | export PATH=$PATH:/home/ec2-user/go/src/github.com/hyperledger/fabric-ca/bin # Add this to your.bash_profile to preserve across sessions 77 | echo "export PATH=\$PATH:/home/ec2-user/go/src/github.com/hyperledger/fabric-ca/bin" >> ~/.bash_profile 78 | cd ~ 79 | 80 | echo "Getting TLS Certificate for Managed Blockchain" 81 | aws s3 cp s3://us-east-1.managedblockchain/etc/managedblockchain-tls-chain.pem /home/ec2-user/managedblockchain-tls-chain.pem 82 | 83 | echo "Checking out Fabric Samples" 84 | cd ~ 85 | git clone --single-branch --branch release-1.2 https://github.com/hyperledger/fabric-samples.git 86 | 87 | echo "Checking out the Workshop" 88 | cd ~/environment/ 89 | #git clone XYC 90 | 91 | echo "Downloading Fabric Repo" 92 | wget https://github.com/hyperledger/fabric/archive/v1.4.2.tar.gz 93 | tar xvzf v1.4.2.tar.gz 94 | mkdir -p ~/go/src/github.com/hyperledger 95 | mv fabric-1.4.2 ~/go/src/github.com/hyperledger/fabric 96 | rm v1.4.2.tar.gz 97 | 98 | echo "Creating CLI docker compose file at ~/docker-compose-cli.yaml" 99 | cat > ~/docker-compose-cli.yaml << EOF 100 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 101 | # 102 | # Licensed under the Apache License, Version 2.0 (the "License"). 103 | # You may not use this file except in compliance with the License. 104 | # A copy of the License is located at 105 | # 106 | # http://www.apache.org/licenses/LICENSE-2.0 107 | # 108 | # or in the "license" file accompanying this file. This file is distributed 109 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 110 | # express or implied. See the License for the specific language governing 111 | # permissions and limitations under the License. 112 | 113 | version: '2' 114 | services: 115 | cli: 116 | container_name: cli 117 | image: hyperledger/fabric-tools:1.2.0 118 | tty: true 119 | environment: 120 | - GOPATH=/opt/gopath 121 | - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock 122 | - CORE_LOGGING_LEVEL=info # Set logging level to debug for more verbose logging 123 | - CORE_PEER_ID=cli 124 | - CORE_CHAINCODE_KEEPALIVE=10 125 | working_dir: /opt/home 126 | command: /bin/bash 127 | volumes: 128 | - /var/run/:/host/var/run/ 129 | - /home/ec2-user/go/:/opt/gopath/ 130 | - /home/ec2-user:/opt/home 131 | EOF 132 | 133 | echo "Pulling needed docker images. " 134 | docker pull hyperledger/fabric-tools:1.2.0 135 | 136 | echo "Bringing up Hyperledger Fabric CLI" 137 | docker-compose -f ~/docker-compose-cli.yaml up & 138 | 139 | sleep 3 140 | 141 | echo "Creating utils." 142 | BIN_DIRECTORY=/home/ec2-user/bin 143 | if [ ! -d $BIN_DIRECTORY ]; then 144 | mkdir -p $BIN_DIRECTORY 145 | fi 146 | 147 | cat > $BIN_DIRECTORY/peer << EOF 148 | source ~/fabric_exports 149 | docker exec -e "CORE_PEER_TLS_ENABLED=true" -e "CORE_PEER_TLS_ROOTCERT_FILE=/opt/home/managedblockchain-tls-chain.pem" -e "CORE_PEER_LOCALMSPID=\$MSP" -e "CORE_PEER_MSPCONFIGPATH=\$MSP_PATH" -e "CORE_PEER_ADDRESS=\$PEERSERVICEENDPOINT" cli peer \$* 150 | EOF 151 | chmod +x $BIN_DIRECTORY/peer 152 | 153 | cat > $BIN_DIRECTORY/configtxgen << EOF 154 | source ~/fabric_exports 155 | docker exec cli configtxgen \$* 156 | EOF 157 | chmod +x $BIN_DIRECTORY/configtxgen 158 | 159 | echo "We have setup the following utilities:" 160 | echo "1) fabric-ca-client -> Your CA Client" 161 | echo "2) Docker CLI helper script." 162 | 163 | # Adding workshop scripts bin path 164 | 165 | echo "==========================================================================" 166 | echo "Completed successfully. Please run:" 167 | echo "1) source ~/.bash_profile" 168 | echo "2) ~/environment/BanktransferBlockchainWorkshop/setup/setup_fabric_environment.py to finish setting up this environment." 169 | echo "==========================================================================" -------------------------------------------------------------------------------- /setup/setup_fabric_environment.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). 5 | # You may not use this file except in compliance with the License. 6 | # A copy of the License is located at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # or in the "license" file accompanying this file. This file is distributed 11 | # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | # express or implied. See the License for the specific language governing 13 | # permissions and limitations under the License. 14 | 15 | import boto3; 16 | import sys, re, optparse 17 | import copy, datetime, time 18 | import requests 19 | import random 20 | import string 21 | import os 22 | from os.path import expanduser 23 | import subprocess 24 | import shutil 25 | 26 | assert sys.version_info > (3,0) 27 | 28 | ambClient = boto3.client('managedblockchain') 29 | ec2Client = boto3.client('ec2') 30 | secretsManagerClient = boto3.client("secretsmanager") 31 | s3Client = boto3.client("s3") 32 | 33 | #Constants 34 | certificateBucket = "reinvent2019-amb-artifacts-us-east-1" 35 | 36 | #The three variables below are global that holds the context that we will be using through out this file. 37 | blockchainNetwork = None 38 | networkDetails = None 39 | 40 | blockchainMember = None 41 | memberDetails = None 42 | 43 | peer = None 44 | 45 | #Generates a random string, which is used for generating Tokens. 46 | def randomString(stringLength=10): 47 | """Generate a random string of fixed length """ 48 | letters = string.ascii_lowercase 49 | return ''.join(random.choice(letters) for i in range(stringLength)) 50 | 51 | 52 | def runCommand(args = []): 53 | print("Executing the following Command:" + ' '.join(args)) 54 | 55 | process = subprocess.Popen(args, 56 | stdout=subprocess.PIPE, 57 | universal_newlines=True) 58 | 59 | return_code = None 60 | while True: 61 | output = process.stdout.readline() 62 | print(output.strip()) 63 | # Do something else 64 | return_code = process.poll() 65 | if return_code is not None: 66 | # Process has finished, read rest of the output 67 | for output in process.stdout.readlines(): 68 | print(output.strip()) 69 | break 70 | return return_code 71 | 72 | #============================= Network Selection ================================ 73 | blockchainNetworks = ambClient.list_networks(Status='AVAILABLE') 74 | 75 | if len(blockchainNetworks) == 0: 76 | print("You currently are not a member of any Amazon Managed Blockchain Networks.") 77 | exit -1 78 | 79 | print("--------------------------------------------------------") 80 | print("Amazon Managed Blockchain Network Selection: ") 81 | print("----------------------------------------------------------") 82 | if len(blockchainNetworks['Networks']) == 0: 83 | print("You are not part of a Blockchain Network. Please join one.") 84 | sys.exit() 85 | elif len(blockchainNetworks['Networks']) == 1: 86 | print("You currently are a member of only one Amazon Managed Blockchain Network.") 87 | print("Would you like to use Network: " + blockchainNetworks['Networks'][0]['Name'] + " [y/n]") 88 | tryAgain = True 89 | while tryAgain is True: 90 | choice = input() 91 | if choice == 'y' or choice == 'yes': 92 | blockchainNetwork = blockchainNetworks['Networks'][0] 93 | tryAgain = False 94 | elif choice == 'n' or choice == 'no': 95 | tryAgain = False 96 | print("Exiting.") 97 | else: 98 | print("Unexpected input. Please try again!") 99 | else: 100 | count = 0 101 | for network in blockchainNetworks['Networks']: 102 | count = count + 1 103 | print (str(count) + ") " + network['Name'] + " (Id: " + network['Id'] + ")") 104 | 105 | print ("\nSelect the network that you want to setup this environment for: ") 106 | tryAgain = True 107 | while tryAgain is True: 108 | try: 109 | choice = input() 110 | blockchainNetwork = blockchainNetworks['Networks'][int(choice) - 1] 111 | tryAgain = False 112 | except KeyboardInterrupt: 113 | sys.exit() 114 | except Exception as e: 115 | print(e) 116 | print ("Invalid Input. Please try again. ") 117 | print ("\n\n You have chosen Network: " + blockchainNetwork['Name']) 118 | 119 | networkDetails = ambClient.get_network(NetworkId=blockchainNetwork['Id']) 120 | 121 | #============================= Member Selection ================================ 122 | blockchainMembers = ambClient.list_members(Status='AVAILABLE', NetworkId=blockchainNetwork['Id'], IsOwned=True) 123 | 124 | print("--------------------------------------------------------") 125 | print("Amazon Managed Blockchain Member Selection: ") 126 | print("--------------------------------------------------------") 127 | if len(blockchainNetworks['Networks']) == 0: 128 | print("You are not part of a Blockchain Network. Please join one.") 129 | sys.exit() 130 | elif len(blockchainMembers['Members']) == 1: 131 | print("This account only has one member to chose from. Would you like to use Member: " + blockchainMembers['Members'][0]['Name'] + " [y/n]") 132 | tryAgain = True 133 | while tryAgain: 134 | choice = input() 135 | if choice == 'y' or choice == 'yes': 136 | blockchainMember = blockchainMembers['Members'][0] 137 | tryAgain = False 138 | elif choice == 'n' or choice == 'no': 139 | tryAgain = False 140 | print("Exiting.") 141 | else: 142 | print("Unexpected input. Please try again!") 143 | else: 144 | count = 0 145 | for member in blockchainMembers['Members']: 146 | count = count + 1 147 | print (str(count) + ") " + member['Name'] + " (Id=" + member['Id'] + ")") 148 | 149 | print ("\nSelect the member that you want to setup this environment for: ") 150 | while tryAgain: 151 | try: 152 | choice = input() 153 | blockchainMember = blockchainMembers['Members'][int(choice) - 1] 154 | except KeyboardInterrupt: 155 | sys.exit() 156 | except Exception as e: 157 | print(e) 158 | print ("Invalid Input. Please try again. ") 159 | print ("\n\n You have chosen Member: " + blockchainMember['Name']) 160 | 161 | memberDetails = ambClient.get_member(NetworkId=blockchainNetwork['Id'], MemberId=blockchainMember['Id']) 162 | 163 | print ("You have chosen Network: " + blockchainNetwork['Name'] + " and Member: " + blockchainMember['Name']) 164 | 165 | #============================= Peer Selection ================================ 166 | #Check if there are any peers, if not, we will ask to create it. 167 | peers = ambClient.list_nodes(Status='AVAILABLE', NetworkId=blockchainNetwork['Id'], MemberId=blockchainMember['Id']) 168 | 169 | print("--------------------------------------------------------") 170 | print("Amazon Managed Blockchain Peer Selection: ") 171 | print("--------------------------------------------------------") 172 | if len(peers['Nodes']) == 0: 173 | print ("You do not have any peers in the select network and membership. Please create one using the following command and wait till its up before rerunnign this script.") 174 | print ("aws managedblockchain create-node --network-id " + blockchainNetwork['Id'] + " --member-id " + blockchainMember['Id'] + " --node-configuration 'InstanceType=bc.t3.medium,AvailabilityZone=us-east-1a'") 175 | sys.exit() 176 | elif len(peers['Nodes']) == 1: 177 | print("This account only has one peer to chose from. Would you like to use Peer: " + peers['Nodes'][0]['Id'] + " [y/n]") 178 | tryAgain = True 179 | while tryAgain: 180 | choice = input() 181 | if choice == 'y' or choice == 'yes': 182 | peer = peers['Nodes'][0] 183 | tryAgain = False 184 | elif choice == 'n' or choice == 'no': 185 | tryAgain = False 186 | print("Exiting.") 187 | else: 188 | print("Unexpected input. Please try again!") 189 | else: 190 | count = 0 191 | for peer in peers['Nodes']: 192 | count = count + 1 193 | print (str(count) + ") " + peer['Id']) 194 | 195 | print ("\nSelect the member that you want to setup this environment for: ") 196 | while tryAgain: 197 | try: 198 | choice = input() 199 | peer = peer['Nodes'][int(choice) - 1] 200 | except KeyboardInterrupt: 201 | sys.exit() 202 | except Exception as e: 203 | print(e) 204 | print ("Invalid Input. Please try again. ") 205 | print ("\n\n You have chosen Peer: " + peer['Name']) 206 | 207 | peerDetails = ambClient.get_node(NetworkId=blockchainNetwork['Id'], MemberId=blockchainMember['Id'], NodeId=peer['Id']) 208 | 209 | print ("We are going to execute a few steps in this script. We are going to: ") 210 | print ("1) Setup a VPC Endpoint so that this machine can talk to your Blockchain Network.") 211 | print ("2) Create an exports file that contains environment variables that will be useful to use so you don't have to remember them.") 212 | print ("3) Obtain an admin Certificate. You will need to provide a password.") 213 | 214 | input("Press Enter to continue...") 215 | 216 | def waitTillVPCEndpointIsReady(vpcEndpointName, vpcId): 217 | keepChecking=True 218 | endPointStatus=None 219 | while (keepChecking): 220 | endPointStatus = ec2Client.describe_vpc_endpoints(Filters=[{'Name': 'service-name', 'Values': [vpcEndpointName]},{'Name': 'vpc-id', 'Values' :[vpcId]}]) 221 | if endPointStatus['VpcEndpoints'][0]['State'] != 'pending': 222 | keepChecking=False 223 | break 224 | print("VPC Endpoint state is : " + endPointStatus['VpcEndpoints'][0]['State'] + ". Waiting. ") 225 | time.sleep(3) 226 | if endPointStatus['VpcEndpoints'][0]['State'] != 'available': 227 | print("VPC Endpoint Creation failed. State = " + endPointStatus['VpcEndpoints'][0]['State']) 228 | sys.exit() 229 | 230 | def update_vpc_security_group(security_group_id): 231 | try: 232 | ec2Client.authorize_security_group_ingress( 233 | GroupId=security_group_id, 234 | IpPermissions=[ 235 | { 236 | 'IpProtocol':'tcp', 237 | 'FromPort':30000, 238 | 'ToPort':31000, 239 | 'UserIdGroupPairs':[{'GroupId':security_group_id}] 240 | } 241 | ]) 242 | except Exception as e: 243 | if 'InvalidPermission.Duplicate' in str(e): 244 | print("Duplicate SG Rule. Ignoring.") 245 | else: 246 | print("Attempt to add SG rule did not succeed.") 247 | print(e) 248 | 249 | 250 | def setup_vpc_endpoint(): 251 | print("--------------------------------------------------------") 252 | print ("Checking VPC Endpoints. ") 253 | print("--------------------------------------------------------") 254 | 255 | vpcEndpointName=networkDetails['Network']['VpcEndpointServiceName'] 256 | 257 | response = requests.get('http://169.254.169.254/latest/meta-data/instance-id') 258 | instance_id = response.text 259 | 260 | instances = ec2Client.describe_instances(InstanceIds=[instance_id]) 261 | if len(instances['Reservations'][0]['Instances']) != 1: 262 | print ("Unable to find instance id when calling EC2! " + instance_id) 263 | #There should only be one instance since we specified the instance id of this instance. 264 | thisEc2Instance = instances['Reservations'][0]['Instances'][0] 265 | 266 | #Check to make sure it doesn't already exist 267 | endpoints = ec2Client.describe_vpc_endpoints(Filters=[{'Name': 'service-name', 'Values': [vpcEndpointName]},{'Name': 'vpc-id', 'Values' :[thisEc2Instance['VpcId']]}]) 268 | 269 | if len(endpoints['VpcEndpoints']) == 1: 270 | print("VPC Endpoint already exists.") 271 | subnetsToAdd=[] 272 | securityGroupsToAdd=[] 273 | if thisEc2Instance['SubnetId'] not in endpoints['VpcEndpoints'][0]['SubnetIds']: 274 | print ("VPC Endpoint does not include this machines subnet. Adding it now.") 275 | subnetsToAdd=[thisEc2Instance['SubnetId']] 276 | 277 | hasSgs=False 278 | for endPointSg in endpoints['VpcEndpoints'][0]['Groups']: 279 | if endPointSg in thisEc2Instance['SecurityGroups']: 280 | hasSgs = True 281 | break 282 | 283 | if hasSgs == False: 284 | print("VPC Endpoint doesn't have a shared SG. Adding now. ") 285 | securityGroupsToAdd=[thisEc2Instance['SecurityGroups'][0]['GroupId']] 286 | 287 | if len(subnetsToAdd) != 0 or len(securityGroupsToAdd) != 0: 288 | ec2Client.modify_vpc_endpoint(VpcEndpointId=endpoints['VpcEndpoints'][0]['VpcEndpointId'], 289 | AddSubnetIds=subnetsToAdd, 290 | AddSecurityGroupIds=securityGroupsToAdd) 291 | 292 | update_vpc_security_group(thisEc2Instance['SecurityGroups'][0]['GroupId']) 293 | 294 | waitTillVPCEndpointIsReady(vpcEndpointName, thisEc2Instance['VpcId']) 295 | print("--------------------------------------------------------") 296 | else: 297 | vpcCreateResponse = ec2Client.create_vpc_endpoint( 298 | VpcEndpointType='Interface', 299 | VpcId=thisEc2Instance['VpcId'], 300 | ServiceName=vpcEndpointName, 301 | SubnetIds=[thisEc2Instance['SubnetId']], 302 | SecurityGroupIds=[thisEc2Instance['SecurityGroups'][0]['GroupId']], 303 | ClientToken=randomString(), 304 | PrivateDnsEnabled=True 305 | ) 306 | 307 | update_vpc_security_group(thisEc2Instance['SecurityGroups'][0]['GroupId']) 308 | 309 | waitTillVPCEndpointIsReady(vpcEndpointName, thisEc2Instance['VpcId']) 310 | 311 | print("--------------------------------------------------------") 312 | print("Successfully created VPC Endpoint. VPC Endpoint is : " + vpcCreateResponse['VpcEndpoint']['VpcEndpointId'] + ". Waiting till its ready") 313 | print("--------------------------------------------------------") 314 | 315 | def create_export_file(): 316 | 317 | print("--------------------------------------------------------") 318 | print("Creating export file") 319 | print("--------------------------------------------------------") 320 | 321 | f = open(expanduser("~") + "/fabric_exports", "w") 322 | f.writelines("export CAFILE=/opt/home/managedblockchain-tls-chain.pem" + "\n") 323 | f.writelines("export NETWORKID=" + networkDetails['Network']['Id'] + "\n") 324 | f.writelines("export MEMBERID=" + memberDetails['Member']['Id'] + "\n") 325 | f.writelines("export MSP=" + blockchainMember['Id'] + "\n") 326 | f.writelines("export MSP_PATH=/opt/home/admin-msp" + "\n") 327 | f.writelines("export ADMINUSER=" + memberDetails['Member']['FrameworkAttributes']['Fabric']['AdminUsername'] + "\n") 328 | f.writelines("export CASERVICEENDPOINT=" + memberDetails['Member']['FrameworkAttributes']['Fabric']['CaEndpoint'] + "\n") 329 | f.writelines("export ORDERER=" + networkDetails['Network']['FrameworkAttributes']['Fabric']['OrderingServiceEndpoint'] + "\n") 330 | f.writelines("export ORDERINGSERVICEENDPOINT=" + networkDetails['Network']['FrameworkAttributes']['Fabric']['OrderingServiceEndpoint'] + "\n") 331 | f.writelines("export PEERNODEID=" + peer['Id'] + "\n") 332 | f.writelines("export PEER=" + peerDetails['Node']['FrameworkAttributes']['Fabric']['PeerEndpoint'] + "\n") 333 | f.writelines("export PEERSERVICEENDPOINT=" + peerDetails['Node']['FrameworkAttributes']['Fabric']['PeerEndpoint'] + "\n") 334 | f.writelines("export PEEREVENTENDPOINT="+ peerDetails['Node']['FrameworkAttributes']['Fabric']['PeerEventEndpoint'] + "\n") 335 | f.writelines("export VPCENDPOINTSERVICENAME=" + networkDetails['Network']['VpcEndpointServiceName'] + "\n") 336 | f.close() 337 | 338 | print ("Generated ~/fabric_exports. Showing contents of the file:") 339 | with open(expanduser("~") + '/fabric_exports', 'r') as f: 340 | print(f.read()) 341 | 342 | print("--------------------------------------------------------") 343 | print("NOTE: run 'source ~/fabric_exports' every time you start a new session. This will not be included in your bash profile.") 344 | input("Please press enter to continue.") 345 | 346 | def obtain_admin_cert(): 347 | print("Creating Admin Certificate") 348 | print("--------------------------------------------------------") 349 | 350 | print("Please enter your Admin password. Default password is 'Admin123'. :") 351 | adminPass = input() 352 | 353 | if len(adminPass) == 0: 354 | adminPass = "Admin123" 355 | 356 | binaryLocation="fabric-ca-client" 357 | # Check if we can find the hardcoded path, if so, use it. Otherwise, let the os find it in $PATH 358 | if os.path.exists("/home/ec2-user/go/src/github.com/hyperledger/fabric-ca/bin/fabric-ca-client") == True: 359 | binaryLocation="/home/ec2-user/go/src/github.com/hyperledger/fabric-ca/bin/fabric-ca-client" 360 | 361 | command = [binaryLocation, "enroll" 362 | , "-u", "https://" + memberDetails['Member']['FrameworkAttributes']['Fabric']['AdminUsername'] + ":" + adminPass + "@" + memberDetails['Member']['FrameworkAttributes']['Fabric']['CaEndpoint'] 363 | , "--tls.certfiles", "/home/ec2-user/managedblockchain-tls-chain.pem" 364 | , "-M", "/home/ec2-user/admin-msp"] 365 | 366 | try: 367 | print ("Executing: " + ' '.join(command)) 368 | subprocess.check_output(command, stderr=subprocess.STDOUT) 369 | except subprocess.CalledProcessError as e: 370 | print ("Failed to generate Admin Certificate! Please check output.") 371 | print (e) 372 | sys.exit() 373 | 374 | #Delete admin certs dir because we are going to create it. 375 | shutil.rmtree(expanduser("~") + "/admin-msp/admincerts", ignore_errors=True) 376 | #Now we have to fix up the certs a bit 377 | shutil.copytree(expanduser("~") + "/admin-msp/signcerts", expanduser("~") + "/admin-msp/admincerts") 378 | 379 | print("--------------------------------------------------------") 380 | print("Completed generating Admin Certificate. This certificate is used for admin operations such as channel creation, or creation of other identity certificates.") 381 | input("Press enter to continue.") 382 | 383 | def store_public_certs(): 384 | print("--------------------------------------------------------") 385 | print("Storing public certificates to S3 to share with other members") 386 | print("--------------------------------------------------------") 387 | 388 | adminMspPath = expanduser("~") + "/admin-msp/" 389 | adminPublicCerts3Key = blockchainNetwork['Id'] + "/" + blockchainMember['Id'] + "/admin-msp/admincerts/cert.pem" 390 | adminPublicCertFile = open(adminMspPath + "admincerts/cert.pem", "rb") 391 | 392 | #Copy Admin Public Cert to S3. 393 | s3Client.put_object(ACL='public-read', Bucket=certificateBucket, 394 | Key=adminPublicCerts3Key, 395 | Body=adminPublicCertFile) 396 | 397 | #Copy CA Public Certs to S3. 398 | caPublicCerts3Key = blockchainNetwork['Id'] + "/" + blockchainMember['Id'] + "/admin-msp/cacerts/ca-" + blockchainMember['Id'] + "-" + blockchainNetwork['Id'] + "-us-east-1-amazonaws-com.pem" 399 | caCertsPath = adminMspPath + "cacerts/" 400 | 401 | print ("Copying CA Certificates from " + caCertsPath + " to S3") 402 | for item in os.listdir(caCertsPath): 403 | fullItemPath = os.path.join(caCertsPath, item) 404 | print ("Found Item: " + item + " in " + caCertsPath) 405 | if os.path.isfile(fullItemPath): 406 | print ("Copying CA Certificate from: " + fullItemPath + " to: s3://" + certificateBucket + "/" + caPublicCerts3Key) 407 | caCertsFile = open(fullItemPath, "rb") 408 | s3Client.put_object(ACL='public-read', 409 | Bucket=certificateBucket, 410 | Key=caPublicCerts3Key, 411 | Body=caCertsFile) 412 | input("Completed copying public certificates to S3. Please enter to continue... ") 413 | 414 | def store_member_information(): 415 | peerAddress = peerDetails['Node']['FrameworkAttributes']['Fabric']['PeerEndpoint'] 416 | s3Client.put_object(ACL='public-read', 417 | Body=peerAddress.encode(), 418 | Bucket=certificateBucket, 419 | Key=blockchainNetwork['Id'] + "/" + blockchainMember['Id'] + "/peer_address.txt" 420 | ) 421 | print("Copied Peer Address to S3 to: s3://" + certificateBucket + "/" + blockchainNetwork['Id'] + "/" + blockchainMember['Id'] + "/peer_address.txt") 422 | input("Press Enter to continue...") 423 | 424 | 425 | setup_vpc_endpoint() 426 | create_export_file() 427 | obtain_admin_cert() 428 | store_public_certs() 429 | store_member_information() 430 | 431 | print("All setup has been completed! Please run 'source ~/fabric_exports to pick up the environment variables. ") -------------------------------------------------------------------------------- /solution/transfer.go: -------------------------------------------------------------------------------- 1 | package bank 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "github.com/hyperledger/fabric/common/util" 7 | "github.com/hyperledger/fabric/core/chaincode/shim" 8 | sc "github.com/hyperledger/fabric/protos/peer" 9 | "github.com/shopspring/decimal" 10 | ) 11 | 12 | type transferEvent struct { 13 | FromAccNumber string `json:"FromAccNumber"` 14 | FromBankID string `json:"FromBankID"` 15 | ToAccNumber string `json:"ToAccNumber"` 16 | ToBankID string `json:"ToBankID"` 17 | Amount string `json:"Amount"` 18 | } 19 | 20 | // Transfer funds from one account to another given four arguments: Payers account Id, Payees bank, 21 | // Payees account Id, and the amount to transfer. The amount must be a positive number. 22 | // If the payer and payee accounts belong to the same bank this will perform an intrabank transfer 23 | // otherwise this will initiate an interbank transfer 24 | // Transferring between accounts at the same bank, but with different currencies requires a Forex contract 25 | // params: fromAccount, toBank, toAccount, amount 26 | func (s *BankChaincode) transfer(stub shim.ChaincodeStubInterface, args []string) sc.Response { 27 | if len(args) != 4 { 28 | return shim.Error("Incorret number of args. Expecting 4: fromAccount, toBank, toAccount, amount") 29 | } 30 | 31 | //sorting arguments 32 | fromAccNum := args[0] 33 | toBankID := args[1] 34 | toAccNum := args[2] 35 | amountAsString := args[3] 36 | amount, err := decimal.NewFromString(args[3]) 37 | 38 | //validate amount is actually a number 39 | if err != nil { 40 | return shim.Error("Unable to parse amount: " + amountAsString) 41 | } 42 | 43 | if amount.LessThanOrEqual(decimal.NewFromFloat(0.0)) { 44 | return shim.Error("Second argument (Amount to deposit) must be a positive number") 45 | } 46 | 47 | //get the fromAccount 48 | fromAccountAsByes := s.queryAccount(stub, []string{fromAccNum}).Payload 49 | fromAccount := &account{} 50 | err = json.Unmarshal(fromAccountAsByes, fromAccount) 51 | 52 | if err != nil { 53 | return shim.Error("Unable to retrieve to account ID from ledger " + err.Error()) 54 | } 55 | 56 | // check if funds are available 57 | if fromAccount.Balance.Cmp(amount) == -1 { 58 | return shim.Error("Account has insufficient funds") 59 | } 60 | 61 | //query our bank to get the ID of the institution 62 | bankAsBytes, err := stub.GetState("bank") 63 | 64 | if err != nil { 65 | return shim.Error("Unable to retrieve bank ID from ledger " + err.Error()) 66 | } 67 | 68 | thisBank := &bank{} 69 | err = json.Unmarshal(bankAsBytes, thisBank) 70 | if err != nil { 71 | return shim.Error("Unable to retrieve bank ID from ledger " + err.Error()) 72 | } 73 | 74 | // is this an interbank transfer? check if recipient bank is not this bank 75 | if toBankID != thisBank.ID { 76 | // initiate interbank transfer 77 | 78 | if thisBank.InterbankContract == "" { 79 | return shim.Error("Unable to perform interbank transfer - no interbankchaincode provided") 80 | } 81 | 82 | stringArgs := []string{"interbankTransfer", toAccNum, toBankID, amountAsString, fromAccount.Currency} 83 | 84 | response := stub.InvokeChaincode(thisBank.InterbankContract, util.ArrayToChaincodeArgs(stringArgs), "") 85 | 86 | if response.Status != shim.OK { 87 | return shim.Error("Unable to invoke interbank transfer contract" + err.Error()) 88 | } 89 | 90 | fromAccount.Balance = fromAccount.Balance.Sub(amount) 91 | 92 | fromAccountAsByes, _ = json.Marshal(fromAccount) 93 | err = stub.PutState(fromAccNum, fromAccountAsByes) 94 | if err != nil { 95 | return shim.Error("Error trying to commit account to ledger" + err.Error()) 96 | } 97 | 98 | //write out an event of the transfer 99 | event := &transferEvent{FromAccNumber: fromAccount.AccNumber, FromBankID: thisBank.ID, ToBankID: toBankID, ToAccNumber: toAccNum, Amount: amount.String()} 100 | eventBytes, _ := json.Marshal(event) 101 | stub.SetEvent("transfer-event", eventBytes) 102 | 103 | return (shim.Success(nil)) 104 | } 105 | 106 | //if not inter bank transfer, perform an intra bank transfer 107 | toAccountAsByes := s.queryAccount(stub, []string{toAccNum}).Payload 108 | toAccount := &account{} 109 | err = json.Unmarshal(toAccountAsByes, toAccount) 110 | 111 | if err != nil { 112 | return shim.Error("Unable to retrieve to account ID from ledger " + err.Error() + string(toAccountAsByes)) 113 | } 114 | 115 | //check if from and to accounts use the same currency 116 | var exchangeRate decimal.Decimal 117 | if fromAccount.Currency == toAccount.Currency { 118 | exchangeRate = decimal.NewFromFloat(1.0) 119 | } else { 120 | // call handler function to invoke Forex chaincode 121 | exchangeRateAsFloat, err := getCurrencyConversion(stub, thisBank.ForexContract, fromAccount.Currency, toAccount.Currency) 122 | 123 | if err != nil { 124 | return shim.Error("Unable to perform currency conversion:" + err.Error()) 125 | } 126 | 127 | exchangeRate = decimal.NewFromFloat(exchangeRateAsFloat) 128 | } 129 | 130 | //update balances 131 | fromAccount.Balance = fromAccount.Balance.Sub(amount) 132 | toAccount.Balance = toAccount.Balance.Add(amount.Mul(exchangeRate)) 133 | 134 | // write changes to ledger 135 | fromAccountAsByes, _ = json.Marshal(fromAccount) 136 | err = stub.PutState(fromAccNum, fromAccountAsByes) 137 | if err != nil { 138 | return shim.Error("Error trying to commit account to ledger" + err.Error()) 139 | } 140 | 141 | toAccountAsByes, _ = json.Marshal(toAccount) 142 | err = stub.PutState(toAccNum, toAccountAsByes) 143 | if err != nil { 144 | return shim.Error("Error trying to commit account to ledger" + err.Error()) 145 | } 146 | 147 | //write out an event of the transfer 148 | event := &transferEvent{FromAccNumber: fromAccount.AccNumber, FromBankID: thisBank.ID, ToBankID: toBankID, ToAccNumber: toAccNum, Amount: amount.String()} 149 | eventBytes, _ := json.Marshal(event) 150 | stub.SetEvent("transfer-event", eventBytes) 151 | return (shim.Success(nil)) 152 | } 153 | 154 | // } 155 | 156 | func getCurrencyConversion(stub shim.ChaincodeStubInterface, forexContract string, baseCurrency string, counterCurrency string) (float64, error) { 157 | 158 | if forexContract == "" { 159 | return 0.0, errors.New("Forex contract is empty, unable to complete transaction") 160 | } 161 | 162 | // invoke the forex contract to get the exchange rate for the pair 163 | stringArgs := []string{"getForexPair", baseCurrency, counterCurrency} 164 | args := util.ArrayToChaincodeArgs(stringArgs) 165 | response := stub.InvokeChaincode(forexContract, args, "") 166 | 167 | if response.Status != shim.OK { 168 | return 0.0, errors.New("Unable to get exchange rate from Forex Contract" + response.Message) 169 | } 170 | 171 | responseForex := &forexPair{} 172 | err := json.Unmarshal(response.GetPayload(), responseForex) 173 | 174 | if err != nil { 175 | return 0.0, errors.New("Unable to unmarshal exchange rate from Forex Contract" + err.Error()) 176 | } 177 | 178 | return responseForex.Rate, nil 179 | } 180 | -------------------------------------------------------------------------------- /ui/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | speed-measure-plugin*.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /ui/README.md: -------------------------------------------------------------------------------- 1 | # Ui 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.3.18. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /ui/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ui": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/ui", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "aot": true, 22 | "assets": [ 23 | "src/favicon.ico", 24 | "src/assets" 25 | ], 26 | "styles": [ 27 | "src/styles.css" 28 | ], 29 | "scripts": [] 30 | }, 31 | "configurations": { 32 | "production": { 33 | "fileReplacements": [ 34 | { 35 | "replace": "src/environments/environment.ts", 36 | "with": "src/environments/environment.prod.ts" 37 | } 38 | ], 39 | "optimization": true, 40 | "outputHashing": "all", 41 | "sourceMap": false, 42 | "extractCss": true, 43 | "namedChunks": false, 44 | "aot": true, 45 | "extractLicenses": true, 46 | "vendorChunk": false, 47 | "buildOptimizer": false, 48 | "budgets": [ 49 | { 50 | "type": "initial", 51 | "maximumWarning": "2mb", 52 | "maximumError": "5mb" 53 | }, 54 | { 55 | "type": "anyComponentStyle", 56 | "maximumWarning": "6kb", 57 | "maximumError": "10kb" 58 | } 59 | ] 60 | } 61 | } 62 | }, 63 | "serve": { 64 | "builder": "@angular-devkit/build-angular:dev-server", 65 | "options": { 66 | "browserTarget": "ui:build" 67 | }, 68 | "configurations": { 69 | "production": { 70 | "browserTarget": "ui:build:production" 71 | } 72 | } 73 | }, 74 | "extract-i18n": { 75 | "builder": "@angular-devkit/build-angular:extract-i18n", 76 | "options": { 77 | "browserTarget": "ui:build" 78 | } 79 | }, 80 | "test": { 81 | "builder": "@angular-devkit/build-angular:karma", 82 | "options": { 83 | "main": "src/test.ts", 84 | "polyfills": "src/polyfills.ts", 85 | "tsConfig": "tsconfig.spec.json", 86 | "karmaConfig": "karma.conf.js", 87 | "assets": [ 88 | "src/favicon.ico", 89 | "src/assets" 90 | ], 91 | "styles": [ 92 | "src/styles.css" 93 | ], 94 | "scripts": [] 95 | } 96 | }, 97 | "lint": { 98 | "builder": "@angular-devkit/build-angular:tslint", 99 | "options": { 100 | "tsConfig": [ 101 | "tsconfig.app.json", 102 | "tsconfig.spec.json", 103 | "e2e/tsconfig.json" 104 | ], 105 | "exclude": [ 106 | "**/node_modules/**" 107 | ] 108 | } 109 | }, 110 | "e2e": { 111 | "builder": "@angular-devkit/build-angular:protractor", 112 | "options": { 113 | "protractorConfig": "e2e/protractor.conf.js", 114 | "devServerTarget": "ui:serve" 115 | }, 116 | "configurations": { 117 | "production": { 118 | "devServerTarget": "ui:serve:production" 119 | } 120 | } 121 | } 122 | } 123 | }}, 124 | "defaultProject": "ui" 125 | } 126 | -------------------------------------------------------------------------------- /ui/browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /ui/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /ui/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('ui app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /ui/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ui/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ui/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/ui'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~12.0.5", 15 | "@angular/common": "~12.0.5", 16 | "@angular/compiler": "~12.0.5", 17 | "@angular/core": "~12.0.5", 18 | "@angular/forms": "~12.0.5", 19 | "@angular/platform-browser": "~12.0.5", 20 | "@angular/platform-browser-dynamic": "~12.0.5", 21 | "@angular/router": "~12.0.5", 22 | "rxjs": "~7.1.0", 23 | "tslib": "^2.3.0", 24 | "zone.js": "~0.11.4" 25 | }, 26 | "devDependencies": { 27 | "@angular-devkit/build-angular": "~12.0.5", 28 | "@angular/cli": "~12.0.5", 29 | "@angular/compiler-cli": "~12.0.5", 30 | "@angular/language-service": "~12.0.5", 31 | "@types/jasmine": "~3.7.7", 32 | "@types/jasminewd2": "~2.0.9", 33 | "@types/node": "~15.12.4", 34 | "codelyzer": "^6.0.2", 35 | "jasmine-core": "~3.7.1", 36 | "jasmine-spec-reporter": "~7.0.0", 37 | "karma": "~6.3.4", 38 | "karma-chrome-launcher": "~3.1.0", 39 | "karma-coverage-istanbul-reporter": "~3.0.3", 40 | "karma-jasmine": "~4.0.1", 41 | "karma-jasmine-html-reporter": "^1.6.0", 42 | "protractor": "~7.0.0", 43 | "ts-node": "~10.0.0", 44 | "tslint": "~6.1.2" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ui/src/app/account/account.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/bank-transfer-blockchain-reinvent2019-workshop/aea7adeafe322f2f31ab73dd35eb22a1a334c3b3/ui/src/app/account/account.component.css -------------------------------------------------------------------------------- /ui/src/app/account/account.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Checking Account
3 |
4 |
Current Balance: {{account.balance}} {{account.currency}}
5 |

Acc Number: {{account.id}}

6 |

{{account.name}}

7 |
8 |
9 | 10 | -------------------------------------------------------------------------------- /ui/src/app/account/account.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AccountComponent } from './account.component'; 4 | 5 | describe('AccountComponent', () => { 6 | let component: AccountComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AccountComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AccountComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/src/app/account/account.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { AccountService } from '../services/account.service'; 3 | 4 | @Component({ 5 | selector: 'app-account', 6 | templateUrl: './account.component.html', 7 | styleUrls: ['./account.component.css'] 8 | }) 9 | export class AccountComponent implements OnInit { 10 | @Input() accNum: string; 11 | account; 12 | 13 | constructor(private accountService: AccountService) { } 14 | 15 | ngOnInit() { 16 | this.account = this.accountService.getAccount(this.accNum) 17 | .subscribe((data)=>{ 18 | console.log(data); 19 | this.account = data; 20 | }); 21 | } 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /ui/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { LoginComponent } from './login/login.component'; 4 | import { HomeComponent } from './home/home.component'; 5 | 6 | 7 | const routes: Routes = [ 8 | { path: '', pathMatch: 'full', redirectTo: 'login'}, 9 | { path: 'login', component: LoginComponent }, 10 | { path: 'home', component: HomeComponent } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forRoot(routes)], 15 | exports: [RouterModule] 16 | }) 17 | export class AppRoutingModule { } -------------------------------------------------------------------------------- /ui/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/bank-transfer-blockchain-reinvent2019-workshop/aea7adeafe322f2f31ab73dd35eb22a1a334c3b3/ui/src/app/app.component.css -------------------------------------------------------------------------------- /ui/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | })); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'ui'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.debugElement.componentInstance; 26 | expect(app.title).toEqual('ui'); 27 | }); 28 | 29 | it('should render title', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('.content span').textContent).toContain('ui app is running!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /ui/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'Account Details'; 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /ui/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { AppComponent } from './app.component'; 6 | import { AccountComponent } from './account/account.component'; 7 | import { TransferComponent } from './transfer/transfer.component'; 8 | import { TransactionsComponent } from './transactions/transactions.component'; 9 | import { LoginComponent } from './login/login.component'; 10 | import { HomeComponent } from './home/home.component'; 11 | import { FormsModule } from '@angular/forms'; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | AppComponent, 16 | AccountComponent, 17 | TransferComponent, 18 | TransactionsComponent, 19 | LoginComponent, 20 | HomeComponent 21 | ], 22 | imports: [ 23 | BrowserModule, 24 | AppRoutingModule, 25 | HttpClientModule, 26 | FormsModule 27 | ], 28 | providers: [], 29 | bootstrap: [AppComponent] 30 | }) 31 | export class AppModule { } 32 | -------------------------------------------------------------------------------- /ui/src/app/home/home.component.css: -------------------------------------------------------------------------------- 1 | .navbar { 2 | background-color: #1976d2; 3 | z-index: 10; 4 | box-shadow: 0 2px 5px 3px rgba(0,0,0,.3); 5 | margin-bottom: 20px; 6 | padding-top: 10px; 7 | } 8 | 9 | .navbar-brand { 10 | color: white; 11 | } 12 | 13 | .sidebar-sticky { 14 | position: fixed; 15 | z-index: 100; 16 | } 17 | 18 | .d-md-block { 19 | display: block !important; 20 | } 21 | .d-none { 22 | display: none !important; 23 | } 24 | 25 | .bankName { 26 | font-family: 'Open Serif', sans-serif; font-weight: 500; 27 | font-style: oblique; 28 | } 29 | 30 | 31 | .showTransfer { 32 | margin-top: 1em; 33 | margin-bottom: 1em; 34 | } -------------------------------------------------------------------------------- /ui/src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
29 | -------------------------------------------------------------------------------- /ui/src/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | describe('HomeComponent', () => { 6 | let component: HomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HomeComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HomeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { environment } from 'src/environments/environment'; 3 | import {Router} from '@angular/router'; 4 | 5 | 6 | @Component({ 7 | selector: 'app-home', 8 | templateUrl: './home.component.html', 9 | styleUrls: ['./home.component.css'] 10 | }) 11 | export class HomeComponent implements OnInit { 12 | 13 | transferVisible = false; 14 | bank_name = environment.bank_name; 15 | accNum; 16 | 17 | constructor(public router: Router) { 18 | this.accNum = this.router.getCurrentNavigation().extras.state.accNum; 19 | } 20 | 21 | ngOnInit() { 22 | 23 | } 24 | 25 | public showTransfer(){ 26 | this.transferVisible = !this.transferVisible 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ui/src/app/login/login.component.css: -------------------------------------------------------------------------------- 1 | .navbar { 2 | background-color: #1976d2; 3 | z-index: 10; 4 | box-shadow: 0 2px 5px 3px rgba(0,0,0,.3); 5 | margin-bottom: 20px; 6 | padding-top: 10px; 7 | } 8 | 9 | .navbar-brand { 10 | color: white; 11 | } 12 | 13 | .sidebar-sticky { 14 | position: fixed; 15 | z-index: 100; 16 | } 17 | 18 | .d-md-block { 19 | display: block !important; 20 | } 21 | .d-none { 22 | display: none !important; 23 | } 24 | 25 | .bankName { 26 | font-family: 'Open Serif', sans-serif; font-weight: 500; 27 | font-style: oblique; 28 | } 29 | 30 | .message { 31 | padding: 1em; 32 | color: red; 33 | } 34 | -------------------------------------------------------------------------------- /ui/src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 |
13 |
14 |
Login
15 |
16 | 17 |
18 | 19 |
20 |
{{message}}
21 | 22 |
23 | -------------------------------------------------------------------------------- /ui/src/app/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LoginComponent } from './login.component'; 4 | 5 | describe('LoginComponent', () => { 6 | let component: LoginComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LoginComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LoginComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {Router} from '@angular/router'; 3 | import { environment } from 'src/environments/environment'; 4 | import { AccountService } from '../services/account.service'; 5 | 6 | 7 | @Component({ 8 | selector: 'app-login', 9 | templateUrl: './login.component.html', 10 | styleUrls: ['./login.component.css'] 11 | }) 12 | export class LoginComponent { 13 | 14 | accNum = ''; 15 | bank_name = environment.bank_name; 16 | message = '' 17 | 18 | constructor(private router: Router, private accountService: AccountService) { 19 | } 20 | 21 | submit() { 22 | console.log(this.accNum) 23 | this.accountService.getAccount(this.accNum) 24 | .subscribe((data)=>{ 25 | console.log(data) 26 | this.message = '' 27 | this.router.navigateByUrl('/home', {state: {accNum: this.accNum}}); 28 | }, 29 | data => { 30 | console.log(data) 31 | this.message = "Unable to load account. Please check your configuration and ensure that the account exists" 32 | 33 | }); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /ui/src/app/services/account.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AccountService } from './account.service'; 4 | 5 | describe('AccountService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: AccountService = TestBed.get(AccountService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /ui/src/app/services/account.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { environment } from 'src/environments/environment'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | export class AccountService { 9 | 10 | constructor(private httpClient: HttpClient) { } 11 | 12 | public getAccount(accNumber: string) { 13 | return this.httpClient.get(`${environment.api_url}/account/${accNumber}`); 14 | } 15 | 16 | public getTransactions(accNumber: string) { 17 | return this.httpClient.get(`${environment.api_url}/transactions/${accNumber}`); 18 | } 19 | 20 | public postTransfer(fromAccNumber: string, toBankID: string, toAccNumber: string, amount: string){ 21 | console.log(fromAccNumber) 22 | console.log(toAccNumber) 23 | var transfer = {FromAccNumber: fromAccNumber, ToBankID: toBankID, ToAccNumber: toAccNumber, Amount: amount} 24 | 25 | console.log(transfer) 26 | return this.httpClient.post(`${environment.api_url}/transfer`, transfer); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ui/src/app/transactions/transactions.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/bank-transfer-blockchain-reinvent2019-workshop/aea7adeafe322f2f31ab73dd35eb22a1a334c3b3/ui/src/app/transactions/transactions.component.css -------------------------------------------------------------------------------- /ui/src/app/transactions/transactions.component.html: -------------------------------------------------------------------------------- 1 |

Recent Transactions

2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
AmountCurrencyTimestamp
{{transaction.Value.balance}} {{transaction.Value.currency}}{{transaction.Timestamp}}
15 | -------------------------------------------------------------------------------- /ui/src/app/transactions/transactions.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TransactionsComponent } from './transactions.component'; 4 | 5 | describe('TransactionsComponent', () => { 6 | let component: TransactionsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ TransactionsComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TransactionsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/src/app/transactions/transactions.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { AccountService } from '../services/account.service'; 3 | 4 | @Component({ 5 | selector: 'app-transactions', 6 | templateUrl: './transactions.component.html', 7 | styleUrls: ['./transactions.component.css'] 8 | }) 9 | 10 | 11 | 12 | export class TransactionsComponent implements OnInit { 13 | 14 | constructor(private accountService: AccountService) { } 15 | @Input() accNum: string; 16 | transactions: any[]; 17 | 18 | ngOnInit() { 19 | return this.accountService.getTransactions(this.accNum) 20 | .subscribe((data)=>{ 21 | console.log(Array.isArray(data)); 22 | 23 | var res = []; 24 | for (var x in data){ 25 | data[x].Value = JSON.parse(data[x].Value); 26 | data[x].Timestamp = new Date(data[x].Timestamp*1000); 27 | res.push(data[x]) 28 | console.log(data[x]) 29 | } 30 | 31 | this.transactions = res.reverse(); 32 | }); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /ui/src/app/transfer/transfer.component.css: -------------------------------------------------------------------------------- 1 | .ok { 2 | padding-top: 1em; 3 | padding-bottom: 1em; 4 | 5 | } 6 | 7 | .error { 8 | padding-top: 1em; 9 | padding-bottom: 1em; 10 | color: red; 11 | } 12 | -------------------------------------------------------------------------------- /ui/src/app/transfer/transfer.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
Make a Payment
5 |
6 | 7 |
8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 |
26 |
27 |
28 | 29 |
{{message}}
-------------------------------------------------------------------------------- /ui/src/app/transfer/transfer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TransferComponent } from './transfer.component'; 4 | 5 | describe('TransferComponent', () => { 6 | let component: TransferComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ TransferComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TransferComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/src/app/transfer/transfer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input} from '@angular/core'; 2 | import { AccountService } from '../services/account.service'; 3 | 4 | 5 | @Component({ 6 | selector: 'app-transfer', 7 | templateUrl: './transfer.component.html', 8 | styleUrls: ['./transfer.component.css'] 9 | }) 10 | export class TransferComponent implements OnInit { 11 | 12 | constructor(private accountService: AccountService) { } 13 | 14 | amount; 15 | toAccNum; 16 | toBankID; 17 | message; 18 | status; 19 | @Input() fromAccNum: string; 20 | show: boolean; 21 | 22 | ngOnInit() { 23 | this.show = true; 24 | } 25 | 26 | submit() { 27 | 28 | this.accountService.postTransfer(this.fromAccNum, this.toBankID, this.toAccNum, this.amount).subscribe(res => { 29 | console.log(res); 30 | this.message='Transfer Complete' 31 | this.show=false 32 | this.status='ok' 33 | 34 | }, 35 | err => { 36 | console.log(err); 37 | this.message='Unable to make tranfer. Check the API configuraiton and ensure the tranfer details are correct.' 38 | this.show=false 39 | this.status='error' 40 | 41 | } 42 | ); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /ui/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/bank-transfer-blockchain-reinvent2019-workshop/aea7adeafe322f2f31ab73dd35eb22a1a334c3b3/ui/src/assets/.gitkeep -------------------------------------------------------------------------------- /ui/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | api_url : 'http://34.224.38.170:8081', 4 | bank_name: 'REPLACE WITH YOUR BANK NAME' 5 | }; 6 | -------------------------------------------------------------------------------- /ui/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: true, 7 | api_url : 'http://34.224.38.170:8081', 8 | bank_name: 'REPLACE WITH YOUR BANK NAME' 9 | }; 10 | 11 | /* 12 | * For easier debugging in development mode, you can import the following file 13 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 14 | * 15 | * This import should be commented out in production mode because it will have a negative impact 16 | * on performance if an error is thrown. 17 | */ 18 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 19 | -------------------------------------------------------------------------------- /ui/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/bank-transfer-blockchain-reinvent2019-workshop/aea7adeafe322f2f31ab73dd35eb22a1a334c3b3/ui/src/favicon.ico -------------------------------------------------------------------------------- /ui/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ui 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ui/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /ui/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /ui/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /ui/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /ui/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.ts" 13 | ], 14 | "exclude": [ 15 | "src/test.ts", 16 | "src/**/*.spec.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | }, 22 | "angularCompilerOptions": { 23 | "fullTemplateTypeCheck": true, 24 | "strictInjectionParameters": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ui/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /ui/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warning" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [ 13 | true, 14 | "attribute", 15 | "app", 16 | "camelCase" 17 | ], 18 | "component-selector": [ 19 | true, 20 | "element", 21 | "app", 22 | "kebab-case" 23 | ], 24 | "import-blacklist": [ 25 | true, 26 | "rxjs/Rx" 27 | ], 28 | "interface-name": false, 29 | "max-classes-per-file": false, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-consecutive-blank-lines": false, 47 | "no-console": [ 48 | true, 49 | "debug", 50 | "info", 51 | "time", 52 | "timeEnd", 53 | "trace" 54 | ], 55 | "no-empty": false, 56 | "no-inferrable-types": [ 57 | true, 58 | "ignore-params" 59 | ], 60 | "no-non-null-assertion": true, 61 | "no-redundant-jsdoc": true, 62 | "no-switch-case-fall-through": true, 63 | "no-var-requires": false, 64 | "object-literal-key-quotes": [ 65 | true, 66 | "as-needed" 67 | ], 68 | "object-literal-sort-keys": false, 69 | "ordered-imports": false, 70 | "quotemark": [ 71 | true, 72 | "single" 73 | ], 74 | "trailing-comma": false, 75 | "no-conflicting-lifecycle": true, 76 | "no-host-metadata-property": true, 77 | "no-input-rename": true, 78 | "no-inputs-metadata-property": true, 79 | "no-output-native": true, 80 | "no-output-on-prefix": true, 81 | "no-output-rename": true, 82 | "no-outputs-metadata-property": true, 83 | "template-banana-in-box": true, 84 | "template-no-negated-async": true, 85 | "use-lifecycle-interface": true, 86 | "use-pipe-transform-interface": true 87 | }, 88 | "rulesDirectory": [ 89 | "codelyzer" 90 | ] 91 | } --------------------------------------------------------------------------------