From 8245252acc61c2d690220a724f43d6c1bbfe75cc Mon Sep 17 00:00:00 2001 From: wenjian3 Date: Thu, 21 Mar 2019 20:04:46 -0400 Subject: [PATCH] [FABN-1184] Implement fabtoken sample app - implement sample app to issue, transfer, redeem and list tokens - change basic-network/crypto-config.yaml user count to 2 - regenerate crypto and channel config via generate.sh - add fabtoken/README.md Change-Id: I8d270c95b29e4af9c432fb05e7e8dc6be7bbc069 Signed-off-by: Wenjian Qiao --- basic-network/config/channel.tx | Bin 421 -> 421 bytes basic-network/config/genesis.block | Bin 8415 -> 8419 bytes basic-network/crypto-config.yaml | 2 +- ...23a9028332a846f05c5feef48f0e56cd74a6b1d_sk | 5 - ...f75b24fbc7f10d14b1d24874ae29d06068f65b3_sk | 5 + .../example.com/ca/ca.example.com-cert.pem | 26 +- .../msp/admincerts/Admin@example.com-cert.pem | 22 +- .../msp/cacerts/ca.example.com-cert.pem | 26 +- .../msp/tlscacerts/tlsca.example.com-cert.pem | 18 +- .../msp/admincerts/Admin@example.com-cert.pem | 22 +- .../msp/cacerts/ca.example.com-cert.pem | 26 +- ...0a4497bdc354767d44e6d85b7326903c38f0df8_sk | 5 + ...5f3e54f7af2e4571d29275708c18b7331a8103b_sk | 5 - .../signcerts/orderer.example.com-cert.pem | 14 +- .../msp/tlscacerts/tlsca.example.com-cert.pem | 18 +- .../orderers/orderer.example.com/tls/ca.crt | 18 +- .../orderer.example.com/tls/server.crt | 14 +- .../orderer.example.com/tls/server.key | 6 +- ...6de813fd9d39302dbd8dc2a0b5535b31619db9f_sk | 5 - ...e3d0f7d4e2df771a6a7662dd6d6e0e1ac2fd00a_sk | 5 + .../tlsca/tlsca.example.com-cert.pem | 18 +- .../msp/admincerts/Admin@example.com-cert.pem | 22 +- .../msp/cacerts/ca.example.com-cert.pem | 26 +- ...fae65d24b11b4eac59b53304e19107d08e6f19f_sk | 5 - ...88a1798db622d7e4d7606e505a8f944fcb446f1_sk | 5 + .../msp/signcerts/Admin@example.com-cert.pem | 22 +- .../msp/tlscacerts/tlsca.example.com-cert.pem | 18 +- .../users/Admin@example.com/tls/ca.crt | 18 +- .../users/Admin@example.com/tls/client.crt | 14 +- .../users/Admin@example.com/tls/client.key | 6 +- ...0315de6d6509a12ae17db15b1b7209c75ca27d2_sk | 5 - ...4459e03526f717dbebaba1fd572da030cecd2d9_sk | 5 + .../ca/ca.org1.example.com-cert.pem | 26 +- .../Admin@org1.example.com-cert.pem | 16 +- .../msp/cacerts/ca.org1.example.com-cert.pem | 26 +- .../tlsca.org1.example.com-cert.pem | 16 +- .../Admin@org1.example.com-cert.pem | 16 +- .../msp/cacerts/ca.org1.example.com-cert.pem | 26 +- ...a60d0903773dfb89e7f6875b38c1bd35375223c_sk | 5 + ...a71328346d467cf9ce32b59efebfe5ad8491707_sk | 5 - .../signcerts/peer0.org1.example.com-cert.pem | 24 +- .../tlsca.org1.example.com-cert.pem | 16 +- .../peers/peer0.org1.example.com/tls/ca.crt | 16 +- .../peer0.org1.example.com/tls/server.crt | 16 +- .../peer0.org1.example.com/tls/server.key | 6 +- ...862ac2ea762a9efe8ff54c9444a0fafc3c945bf_sk | 5 + ...626c1d6bb9bde197fd52d9e74715fc575425993_sk | 5 - .../tlsca/tlsca.org1.example.com-cert.pem | 16 +- .../Admin@org1.example.com-cert.pem | 16 +- .../msp/cacerts/ca.org1.example.com-cert.pem | 26 +- ...cf51fe7e6f62cdebe6193f070445243aedddee9_sk | 5 + ...e8b503e412fdb598477840617919fa88a346165_sk | 5 - .../signcerts/Admin@org1.example.com-cert.pem | 16 +- .../tlsca.org1.example.com-cert.pem | 16 +- .../users/Admin@org1.example.com/tls/ca.crt | 16 +- .../Admin@org1.example.com/tls/client.crt | 14 +- .../Admin@org1.example.com/tls/client.key | 6 +- .../User1@org1.example.com-cert.pem | 24 +- .../msp/cacerts/ca.org1.example.com-cert.pem | 26 +- ...31361c151463b13979271b86e41d5a3dc3594de_sk | 5 + ...e229c662d0884350b9e3286fcc1d769e22e2746_sk | 5 - .../signcerts/User1@org1.example.com-cert.pem | 24 +- .../tlsca.org1.example.com-cert.pem | 16 +- .../users/User1@org1.example.com/tls/ca.crt | 16 +- .../User1@org1.example.com/tls/client.crt | 24 +- .../User1@org1.example.com/tls/client.key | 6 +- .../User2@org1.example.com-cert.pem | 14 + .../msp/cacerts/ca.org1.example.com-cert.pem | 15 + ...6424f9b1cb6d0f7c1659d083077c1308edcdd51_sk | 5 + .../signcerts/User2@org1.example.com-cert.pem | 14 + .../tlsca.org1.example.com-cert.pem | 15 + .../users/User2@org1.example.com/tls/ca.crt | 15 + .../User2@org1.example.com/tls/client.crt | 14 + .../User2@org1.example.com/tls/client.key | 5 + basic-network/docker-compose.yml | 2 +- fabtoken/README.md | 180 +++++++++ fabtoken/javascript/.gitignore | 8 + fabtoken/javascript/fabtoken.js | 345 ++++++++++++++++++ fabtoken/javascript/package.json | 22 ++ fabtoken/startFabric.sh | 46 +++ 80 files changed, 1155 insertions(+), 457 deletions(-) delete mode 100644 basic-network/crypto-config/ordererOrganizations/example.com/ca/60a3a0436468d6505527325c123a9028332a846f05c5feef48f0e56cd74a6b1d_sk create mode 100644 basic-network/crypto-config/ordererOrganizations/example.com/ca/648c46f45ef1849bf71259e8af75b24fbc7f10d14b1d24874ae29d06068f65b3_sk create mode 100644 basic-network/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/msp/keystore/59a642f746fd7bc4e8980d4ea0a4497bdc354767d44e6d85b7326903c38f0df8_sk delete mode 100644 basic-network/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/msp/keystore/7557b147fe249860e7eae055e5f3e54f7af2e4571d29275708c18b7331a8103b_sk delete mode 100644 basic-network/crypto-config/ordererOrganizations/example.com/tlsca/270655cf5f4b8e67e0f29e7c66de813fd9d39302dbd8dc2a0b5535b31619db9f_sk create mode 100644 basic-network/crypto-config/ordererOrganizations/example.com/tlsca/b78fbb6b1f27efbd694697cbce3d0f7d4e2df771a6a7662dd6d6e0e1ac2fd00a_sk delete mode 100644 basic-network/crypto-config/ordererOrganizations/example.com/users/Admin@example.com/msp/keystore/4f9f167ca424595a28a1451e7fae65d24b11b4eac59b53304e19107d08e6f19f_sk create mode 100644 basic-network/crypto-config/ordererOrganizations/example.com/users/Admin@example.com/msp/keystore/d0d5354320a712722aca5b7a288a1798db622d7e4d7606e505a8f944fcb446f1_sk delete mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/ca/4b8f785ca3d30a7af17a0cfcf0315de6d6509a12ae17db15b1b7209c75ca27d2_sk create mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/ca/76ecb26bf00310f650893c18f4459e03526f717dbebaba1fd572da030cecd2d9_sk create mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/keystore/3edc106f6762315aae4cc735ea60d0903773dfb89e7f6875b38c1bd35375223c_sk delete mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/keystore/9a8eca60905ae4df38b8dc09ea71328346d467cf9ce32b59efebfe5ad8491707_sk create mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/tlsca/962568ddcdcb31b6b364caffc862ac2ea762a9efe8ff54c9444a0fafc3c945bf_sk delete mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/tlsca/f08ad36f8ad883db109c35d22626c1d6bb9bde197fd52d9e74715fc575425993_sk create mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/5ba12183ab07014ba831f9a79cf51fe7e6f62cdebe6193f070445243aedddee9_sk delete mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/c4fc186411097d368184b61aae8b503e412fdb598477840617919fa88a346165_sk create mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/740efb1655d71c3984062726b31361c151463b13979271b86e41d5a3dc3594de_sk delete mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/d0ccda16347d394d9634ad067e229c662d0884350b9e3286fcc1d769e22e2746_sk create mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/users/User2@org1.example.com/msp/admincerts/User2@org1.example.com-cert.pem create mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/users/User2@org1.example.com/msp/cacerts/ca.org1.example.com-cert.pem create mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/users/User2@org1.example.com/msp/keystore/5185716bd707635c718c6c37c6424f9b1cb6d0f7c1659d083077c1308edcdd51_sk create mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/users/User2@org1.example.com/msp/signcerts/User2@org1.example.com-cert.pem create mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/users/User2@org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem create mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/users/User2@org1.example.com/tls/ca.crt create mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/users/User2@org1.example.com/tls/client.crt create mode 100644 basic-network/crypto-config/peerOrganizations/org1.example.com/users/User2@org1.example.com/tls/client.key create mode 100644 fabtoken/README.md create mode 100644 fabtoken/javascript/.gitignore create mode 100644 fabtoken/javascript/fabtoken.js create mode 100644 fabtoken/javascript/package.json create mode 100755 fabtoken/startFabric.sh diff --git a/basic-network/config/channel.tx b/basic-network/config/channel.tx index bc0fd753634255c8516eb6da07ff267728553dc7..e4e8678138427412bb4bea1e7e492c7a60caad6c 100644 GIT binary patch delta 34 qcmZ3=yp)-rYY{V-IF~2~lN1}r;qJ*3`Dab$W6YkoB6V^lqYeO&e+hs9 delta 34 qcmZ3=yp)-rYY{V-IF~2~lN1}rivJTQ^3R$q%9t{7Mb6|(MjZf_2MOK) diff --git a/basic-network/config/genesis.block b/basic-network/config/genesis.block index 013bbe8e5b741d9420092934ba36fbfdded7093e..8f555216e5982549ce290cac93ef9367345b45fc 100644 GIT binary patch literal 8419 zcmeHMTa4paTAt}?dTP(~^tRkHn%UCsGE>X|>ddSiC(dQ1)%Nivj*}cGcAP5`$d|;q z*m2@Z>{v+jJaTzrA9(;CR^qY}h(+*%#A-nT@q)w)2noRhXkie%AT82LTuzcos=BAU zs_E?~)G4W|a{Qlb&iDQQ_XV7tzL$LOdw=+yfB1LlyT1?qwDk2q|EW8_``@4b%CG<9 z!;k(({ph=&-1tx8_aOKk2)+fu&w`&mdG+qAr?*c2Y$Mda`Kl06qzF3T$ z`wtjvT+Zc7COsPG$E9(WX3CVFFOR4!WiSO@r}9~Y8W)UGUeA}ZjFmUWv`$$}F*~MA zI-es>iFfTU7X6>gYTx+MZ#w^`e)|u;UH|3VZ+(C9*FPYB`vLfk2gElY5dZQHc)huw znfSYRh+ldefb+La-zNU+t<%4{2VO(%7V#JNh(EqZ{L#JR^KIjw-THX@Vg*r>-{S;T zlPVGiHDT8ZkR)*}jpN{`X~S4zp)Dzh?zN+7>hVtAE2w&QQirHCgLT%nSCjdy*-{i9 z&e$bVf*5ehfbS}bD8#jHClxe=CxY3Iz_F;V(3L>%4cDg2EC-s(lW-eKk#=m0bxmj>INwbM*a_mgU6cqZRQEI3 z8aJ0GhUmCPt`_M%A*sw@XaCI9_%41nWku7i;EA0S4Cqo);bD6p;AtGaU)phHsu@@I z=fn#!GaIxSXCziaQ%szOE3bi}u|vcnk=?AsHp-WwEr=X<{(me~AtgbUrHaeA%V*mw zMR7Z=n!rGMUF@;zxDF^fZ6Oj-1qKz>K_YZ&e5N<|Y?}^;<$N@ErrG6eij=PIc1%^l zTbeQb1;@usG{cko0KxqBFf2Y6STa_G?Oj;TptapnwcQc+`?r187AmY_^3Wbg^?1Ot zN~a2AAqISm?}18bP^pqE?8LZ{B~!TE4WQ%&$yV&4l!)a@F`3lXIl)2W4h?9 zS4P2gatxp8d&*F(<`R7gyJUPN2G~2`X1fY$;@0*zQ>gvrwU|)SZFX55Frh-pNb{XS zW^P;4N~viTlR{~phmYeuHh7_dX=%^V&r8wZz|q%Jv=@tTm&$m|_LNbTcQg$y^R%JC zQ6;95rUogTQ4*O)S;VtCZrk9t6la}d8xrG$OJNP{ukt{HPFF)JiiLe5kc4>24|>UR z=$1Ez>r9Y3h%5UQRE0RJ4SIQ7UDPJPaOI^*%WI@6v9P^$ic%Ni@R1B+Xo8Mddza5( zf>cXP(g7_*+awAuWzo2&L5;P|^E@uG7daiVY%FsSs>_X>MypM3E%K$fkPjmeaQ%q! z1K8gLdCw71t1}zVomtc>%_{u37AImSLsJ}?nM^CeOtq?64jC=QOHz@}j!Sv4Xsu0; zreZyX&3!^Bv2a`xa4zSdK8UN^Gf^prWp`(ADYHm84+a=iN~glcI7PzJE=`>Stb!og z4iwk-12kDq5~Rkc716=CmdTX$j^5vt@@2s1vdzZWN|r0jZCFMx-xPyJ8P*Z>j+u71 z8p?!7P&kQeR4U3eZTe)XC(JdF4cGI5gMtcADP1A6QHC{lF)u8rO$c$8(M%>cx1d(` zGxe2PWlV8X4MTcXGQ}Q)3Z4zDg=!Tfxl7!++-F}G!^OT2Q7dLO*W;eMk<5Cz(1IU?>7x4_N7bG&278Tj08CGBZC zBHkw6I01JbKKxGh-G>j~JH(uw9S`VT;?Buyc&hV#+`Vy(xeq?iI-WW2>dw^Cx638o zA>PEnpF4ge$JjIQ`r+t~G3O1JV-BC>G3LDC5OX+9;s+msH@5rq{BRn%0rBaF#IJq` z?zVMz>5MKO5Wnyt@vRTGvcS%6i~L{v;PmIu_9EbuGvfR26aVr4#lye9mZLj7=IHV0 zz|l%$$xdo3o2<&F!H-KW+ttRc^fPr!`oa|eu7b@SiE$I8;M#kE9xIhG(V2+#4n(JI zNe(noy)ZjSg*UT+Z+!YxpyUiM8)-7sHSdhmDxmUP`0OTvx*je zn<>s1*cYUZ3asX!xNd|Sam7$%tG**=tt?y0wFMR=R(FZotL}IatEw;5DnVw}&JNSCC0wZte(PF<)wjyP z5)TMdVM}W_qNSI*u{q3_s@d6wY7Rc5dSWwu(N&PB!T$c9dzv7YH^X#aCS0l#k}41N zeGC@Ov^)U~T%BsVz? z2-HbgfsMAo1>UIbOJOihip>TpXgZnC1YT4rlv#0og^d1;g!tEM!PDENr@{qzkTajn z>A1|b`x(1iPzL#IUGk3mJl|$)VQuLAckEU@KY+jvRWp`; zUI2m1hTA0oJxjJ7Z+U(zY6Yw-dIw$c+~u z)y~Db%EZF{()BLZRVEgwp1WAiwCm-}Tmu<;Iz&FX5$i!?WldKj(j&84O%=SBUuxA< zqx8s)#Ak5H#ge;?(9FtgnNsPs9A%;9)VrHrY&V=b66=X1@D+Dan>Fp+R$laz!2+a} z0-~}LZteN}svIVQz!aA9U>wl2qiq=9k4wXieWkn@{_UGs7JPaOyetZ@5%4dI!k0zi z4^tEpw@ha`T7;+n{1R-}x@9lH_7ZGA6xePqMrkwRJTp2`gIN^OA+eLTw-Wm4K zwjSDz(>IAXjvfg%!ybA2+M{nTydmLe0TEv%C_K_f#BBmxjQnW7$40#vCVQ}*h@T+7 zgnNJCl27-@LM}|Y0(e7MSe_q@T+Axh^?U7b76)hP^Z|JL+=t`*(azOs;p*Sq;E`@) zZ|mf+qSJTC^EmSSTV`@^6W)K?gR_qheTg3B5GN?O}^_;u6Gm{rj&Z4jVuH7<^%8)==GD>zP+*eG`{c4e7sHbl5nM}qPY#g4RfLHcE{|o05 B8K3|F literal 8415 zcmeHMON`^ldET8J_qJqC49atU%VkQ^K!aTYd`V@?5*TL1$=03UJ)U<5hDiIc+u3E+!iI3=mo zJ&&Eoj@Jobj2hi+RsHo~vA+8L?;|)neT)0XpWDB`_{O(?^Gjd-&hLKjOMmpk+MA27 zf9=M9W&ir?U%CC^w`l6GAovypA3*Rs;J1!nK6&}{==g&l|KY!V>g-k!_TA78ouM_G zxs$v17_P|koKs|(Y%a%g4DS|cChJ-Z>u`2~w`|^G9p2)(9Elq+99?9X0i7SvR-P@` zIhH!5-pK#l_y6@%Km3h<`tJW6|3hK^ zKQi^*JJcV%4#36NdgxI<`t0dHx@slpsT-e` zNNi#mQWao0R$#Uzp&=Gb6Q|M!Hj!5|Nl%6_t_^KvGPQH%*lJ6u?8DZ+&M`!j)Qyk= zoT(glnaQTh@Tn|9eIF1Q0%Nk>b~dq<7O5{Tanng;tt*(#CQhXN2&$CK z#cn&D_ol_QJ)I13vzy3pH+o4*vPQSfdd*pRXisZ%;4rlnZfc3-?E)%NvfNGWYGzUg zqt^(s7WTOxEF|C@zaeZpwC6g9>8S<7^?T z_FFaR&Zj6_;h4tE49MWX+Q9mDF-jVb;DDD9!~(RsNol(tO?raR?*7$E(nUyXmB{l~ z1R+wO){;?z5!na)_Wt@(BB4y8$r$2oM@ePhK^+OIM4I@4R2y5ho@VOKx`WjGSnxKL zpt%Cb>6{m6KjGS&jy{e07zSj7g`#7&xSI{_K`gZdRTIS+(+gLahUJ2`YG~!KvK$#; zm}}EvBTCy!F?8eESQu8vlSK78eo&;1w5M&RomI0t)+T+WoEsobTx)i7qD%^Gpy~6n{`~AK>5KEp2G1#G~x!e2_^dZ3!K?wndnrLGzRN#|9u*Cp)Mi8{d z=CIuu)FoqD7)y30qfA7ToW5<~gy-FCBh%JrUf$_0tf8iQea!jkcu)quO+8g&MoniFo14>>v{;^@1`W#uHry1pH1oVH0}eFvOe`Zf zis=*wI?K)2M#UW{whKHX)1x^oFh$>~cuTWp**QzG_sb?P#UHYIF*teu{lCk; z@&5a79b!-41UGjv#%SsW@shelT?f~g=(7G7!EIrmQ#ajc8IGLgD40dm@nyg};HF^B zEqgQ>Ek|xdeU<{?7+g2Azn!ICx{P`oye5!#?ktKUV?B4N6Y2vMALGUhJ$44MSa*t902zO|h$^@RQ|pX6oC#iKp+ zcUVp82k(L#+j|JY=4 z5%8n8ssDbP`ta?m%71z48Qn6-89f+XozYbWVTqpC3%I(PvE9C+&v5V2GuqjF(r2_{ zz|KJdNJ??t+dP;mlQS$WiO|}|U_)YRLr^72`bPvNrJ+;9#yF{oB>NY~o0<=k_O{F) zCwPO?Lm6oMXoIyV5%$@?A|++BwUG==R1@KDNkF)wF>++Jdn;$k&AW!w#z@=Mp%m9Z zi;-hh>aDwN&bK->-`+?hKXLYrhgc&DXofEY%N3hQ4o1N)1`t_bY!B>@4*ck3=l)y= zpoW+U8gbYjth#eJ+03f4n9C`q!S`jlL(b>8U^`7mWYbZY6~vy>tIgI19OQ%aoHer& z5S{tD)iu3#v~Ig@GwufVnz!4&ZJ_WYk{vYnVSktF=q+h0>{X^sfDQt?tM zNQs#2WP|~=QU<|RLQ*u8*}(a(1C5nbPxFAz7THdvVFX}r%bAm?IO4dGzl`ccXdYhwhli!P0ViSm{ zcTf*2;xjmDb^4H}AdeF%>6R{B4FPJG4u=TIHr6~S*>b{Y6*p7`EuPnAN83&)NRm@`!wAh&CCUwsAggcWcv`JnhbvxS0`I zxxes6+8|SM*J-+jtUG%GSRRoO|Nd0)m|Iyr$Y0TuDi$V6FM}I-Rp47)L!7a4ReJt!m6g-Mv{Q$C z-EXhNQ1Kg81RG^bgXSD--pJ&v*qc3ya3w^_4Pu1{Z!Q#b$#x-;lW8c%Yz%ztRIAt zJ0%FDewq3OLVBOO&(eC0x^d}^oV;?`u5;zLVM-uvex2Ayx5$|LQ+m|2w z`pSb}ue>7Z(qf{%NYSLHU!`tR;Hu{b+db6lRX5p#4fV8pdeo!42VTGM*M!jBxmAr| zGIG|Wr<=s!IzIG#dM9%cm$`VuoVjrO9#?Vp-k~k^dFq(l({<_^apWEse)n+|r@sVF zN>(%+%{(~q0^(r~rxdlF+5Sbt;W3@Q2R^qm-b8C@T{O^_VeBm9aOd|O|CoF}a~ZCJ zlZ!in{eI|15it)r>J+>Y>|dyV!A*D8+83N)`o#jx(T|<_>Ek`f+?5AO492IMhs1 ` to issue tokens of any +type and quantity as user1 or user2. + +* As an example, the first command issues a token worth 100 US dollars as user1. The +second command issues a token worth 100 Euro's as user2: + +``` +node fabtoken issue user1 USD 100 +node fabtoken issue user1 EURO 200 +``` + +#### List tokens + +After you issue tokens, you can use the list method to query the tokens that you own. Run +the command `node fabtoken list ` You need to use this command to recover the +tokenIDs that you will need to transfer or redeem your tokens. + +* As an example, you can use the command below to list the tokens owned by user1: + +``` +node fabtoken list user1 +``` +* The command returns a list of tokens, with the tokenID consisting of a tx_id and +index. You will need to use these values for future commands. + +``` +[ { id: + { tx_id: 'ab5670d3b20b6247b17a342dd2c5c4416f79db95c168beccb7d32b3dd382e5a5', + index: 0 }, + type: 'EURO', + quantity: '200' }, + { id: + { tx_id: 'c9b1211d9ad809e6ee1b542de6886d8d1d9e1c938d88eff23a3ddb4e8c080e4d', + index: 0 }, + type: 'USD', + quantity: '100' } +] +``` + +#### Transfer tokens + +Tokens can be transferred between users on a channel using the +`node fabtoken transfer ` command. +* `` and `` are the "tx\_id" and "index" that you found using the list +command +* `` is the quantity to be transferred + +Any remaing quantity will be transferred back to the owner by creating a new token with +a new tokenID. +* As an example, the following commands transfers 30 dollars from user1 transfer to user2: + +``` + node fabtoken transfer user1 user2 30 c9b1211d9ad809e6ee1b542de6886d8d1d9e1c938d88eff23a3ddb4e8c080e4d 0 + ``` + +You can run the command `node fabtoken list user2` to verify that user2 now owns a token +worth 30 dollars. You can also run the command `node fabtoken list user1` to verify that +a new token worth 70 dollars now belongs to user1. + + +#### Redeem tokens + +Tokens can be taken out of circulation by being redeemed. Redeemed tokens can no longer +be transfered to any member of the channel. Run the command +`node fabtoken redeem ` to redeem any tokens +belonging to user1 or user2. +* `` and `` are the "tx\_id" and "index" returned from the list command +* `` is the quantity to be redeemed + +Any remaing quantity will be transferred back to the owner with a new tokenID. +* As an example, the following command redeems 10 Euro's belonging to user1: + +``` + node fabtoken redeem user2 10 ab5670d3b20b6247b17a342dd2c5c4416f79db95c168beccb7d32b3dd382e5a5 0 + ``` + +#### Clean up + +If you are finished using the sample application, you can bring down the network and any +accompanying artifacts. + +* Change to `fabric-samples/basic-network` directory +* To stop the network, run `./stop.sh` +* To completely remove all incriminating evidence of the network, run `./teardown.sh` + +## Understanding the `fabtoken.js` application + +You can examine the `fabtoken.js` file to get a better understanding of how the +sample application uses the FabToken API's. + + +1. The `createFabricClient` method creates an instance of the fabric-client, and is +used to connect to the components of your network. + +2. The `createUsers` method uses the certificates generated by the basic network to +create `admin`, `user1` and `user2` users for the application. + +3. To perform token operations, you must create a `TokenClient` instance from a `Client` +object. Make sure the client has set the user context. Below is the code snippet. + +``` + // set user context to the client + await client.setUserContext(user, true); + + // create a TokenClient instance + const tokenClient = client.newTokenClient(channel, 'localhost:7051'); +``` + +4. The `issue` method creates an issue request and submits the request to issue tokens to +your network. + +5. The `list` method submits the request to list tokens from a +given owner, and is used to recover the tokenID if a token is being transfered or redeemed. + +6. The `transfer` method creates a transfer request and submits the request to transfer tokens +between users. + +7. The `redeem` method creates a redeem request and submits the request to redeem a user's +tokens. \ No newline at end of file diff --git a/fabtoken/javascript/.gitignore b/fabtoken/javascript/.gitignore new file mode 100644 index 0000000000..3be8dfd89c --- /dev/null +++ b/fabtoken/javascript/.gitignore @@ -0,0 +1,8 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Dependency directories +node_modules/ +package-lock.json + diff --git a/fabtoken/javascript/fabtoken.js b/fabtoken/javascript/fabtoken.js new file mode 100644 index 0000000000..54d0c6b4bd --- /dev/null +++ b/fabtoken/javascript/fabtoken.js @@ -0,0 +1,345 @@ +'use strict'; +/* +* Copyright IBM Corp All Rights Reserved +* +* SPDX-License-Identifier: Apache-2.0 +*/ +/* + * Chaincode Invoke + */ + +const Fabric_Client = require('fabric-client'); +const path = require('path'); +const util = require('util'); +const os = require('os'); +const fs = require('fs-extra'); + +const channel_name = "mychannel" + +start(); + +async function start() { + console.log('\n\n --- fabtoken.js - start'); + try { + console.log('Setting up client side network objects'); + + // create fabric client and related instances + // starting point for all interactions with the fabric network + const {fabric_client, channel} = createFabricClient(); + + // create users from existing crypto materials + const {admin, user1, user2} = await createUsers(); + + console.log('Successfully setup client side'); + + let operation = null; + let user = null; + const args = []; + + // if there is no argument, it will run demo by calling hardcoded token operations + // if there are arguments, it will invoke corresponding issue, list, transfer, redeem operations + if (process.argv.length == 2) { + demo(fabric_client, channel, admin, user1, user2) + return + } else if (process.argv.length >= 4) { + operation = process.argv[2]; + if (process.argv[3] === 'user1') { + user = user1; + } else if (process.argv[3] === 'user2') { + user = user2; + } else { + throw new Error(util.format('Invalid username "%s". Must be user1 or user2', process.argv[3])); + } + for (let i = 4; i < process.argv.length; i++) { + if (process.argv[i]) { + console.log(' Token arg: ' + process.argv[i]); + args.push(process.argv[i]); + } + } + } else { + throw new Error('Missing required arguments: operation, user'); + } + + console.log('\n\nStart %s token operation', operation); + let result = null; + switch (operation) { + case 'issue': + if (args.length < 2) { + throw new Error('Missing required parameter for issue: token_type, quantity'); + } + result = await issue(fabric_client, channel, admin, user, args); + break; + case 'transfer': + if (args.length < 4) { + throw new Error('Missing required parameters for transfer: recipient, transfer_quantity, tx_id, index'); + } + let recipient + if (args[0] === 'user1') { + recipient = user1; + } else if (args[0] === 'user2') { + recipient = user2; + } else { + throw new Error(util.format('Invalid recipient "%s". Must be user1 or user2', process.argv[3])); + } + // shift out args[0] because recipient object is passed separately + args.shift(); + result = await transfer(fabric_client, channel, user, recipient, args); + break; + case 'redeem': + if (args.length < 3) { + throw new Error('Missing required parameter for redeem: quantity, tx_id, index'); + } + result = await redeem(fabric_client, channel, user, args); + break; + case 'list': + result = await list(fabric_client, channel, user); + break; + default: + throw new Error(' Unknown operation requested: ' + operation); + } + + console.log('End %s token operation, returns\n %s', operation, util.inspect(result, {depth: null})); + + } catch(error) { + console.log('Problem with fabric token ::'+ error.toString()); + process.exit(1); + } + console.log('\n\n --- fabtoken.js - end'); +}; + +// demo invokes token operations using hardcoded parameters +async function demo(client, channel, admin, user1, user2) { + await reset(client, channel, user1, user2); + + console.log('admin issues token to user1, wait 5 seconds for transaction to be committed'); + await issue(client, channel, admin, user1, ['USD', '100']); + await sleep(5000) + + let user1_tokens = await list(client, channel, user1); + console.log('\nuser1 has a token in USD type and 100 quantity after issue:\n%s', util.inspect(user1_tokens, {depth: null})); + + console.log('\nuser1 transfers 30 quantity of the token to user2, wait 5 seconds for transaction to be committed'); + let token_id = user1_tokens[0].id; + await transfer(client, channel, user1, user2, ['30', token_id.tx_id, token_id.index]); + await sleep(5000) + + user1_tokens = await list(client, channel, user1); + console.log('\nuser1 has a token in 70 quantity after transfer:\n%s', util.inspect(user1_tokens, {depth: null})); + + let user2_tokens = await list(client, channel, user2); + console.log('\nuser2 has a token in 30 quantity after transfer:\n%s', util.inspect(user2_tokens, {depth: null})); + + console.log('\nuser1 redeems 10 out of 70 quantity of the token'); + token_id = user1_tokens[0].id; + await redeem(client, channel, user1, ['10', token_id.tx_id, token_id.index]); + + console.log('\nuser2 redeems entire token, wait 5 seconds for transaction to be committed'); + token_id = user2_tokens[0].id; + await redeem(client, channel, user2, ['30', token_id.tx_id, token_id.index]); + await sleep(5000) + + user1_tokens = await list(client, channel, user1); + console.log('\nuser1 has a token in 60 quantity after redeem:\n%s', util.inspect(user1_tokens, {depth: null})); + + user2_tokens = await list(client, channel, user2); + console.log('\nuser2 has no token after redeem:\n%s', util.inspect(user2_tokens, {depth: null})); + + await reset(client, channel, user1, user2); +} + +// reset removes all the existing tokens on the channel to get a fresh env +async function reset(client, channel, user1, user2) { + console.log('\nReset: remove all the tokens on the channel\n'); + + let tokens = await list(client, channel, user1); + for (const token of tokens) { + await redeem(client, channel, user1, [token.quantity, token.id.tx_id, token.id.index]); + } + + tokens = await list(client, channel, user2); + for (const token of tokens) { + await redeem(client, channel, user2, [token.quantity, token.id.tx_id, token.id.index]); + } +} + +// Issue token to the user with args [type, quantity] +// It uses "admin" to issue tokens, but other users can also issue tokens as long as they have the permission. +async function issue(client, channel, admin, user, args) { + console.log('Start token issue with args ' + args); + + await client.setUserContext(admin, true); + + const tokenClient = client.newTokenClient(channel, 'localhost:7051'); + + // build the request to issue tokens to the user + const txId = client.newTransactionID(); + const param = { + owner: user.getIdentity().serialize(), + type: args[0], + quantity: args[1] + }; + const request = { + params: [param], + txId: txId, + }; + + return await tokenClient.issue(request); +} + +// Transfers token from the user to the recipient with args [quantity, tx_id, index] +async function transfer(client, channel, user, recipient, args) { + console.log('Start token transfer with args ' + args); + + await client.setUserContext(user, true); + + const tokenClient = client.newTokenClient(channel, 'localhost:7051'); + + // build the request to transfer tokens to the recipient + const txId = client.newTransactionID(); + const param1 = { + owner: recipient.getIdentity().serialize(), + quantity: args[0] + }; + + const request = { + tokenIds: [{tx_id: args[1], index: parseInt(args[2])}], + params: [param1], + txId: txId, + }; + + return await tokenClient.transfer(request); +} + +// Redeem tokens from the user with args [quantity, tx_id, index] +async function redeem(client, channel, user, args) { + console.log('Start token redeem with args ' + args); + + await client.setUserContext(user, true); + + const tokenClient = client.newTokenClient(channel, 'localhost:7051'); + + // build the request to redeem tokens + const txId = client.newTransactionID(); + const param = { + quantity: args[0] + }; + const request = { + tokenIds: [{tx_id: args[1], index: parseInt(args[2])}], + params: [param], + txId: txId, + }; + + return await tokenClient.redeem(request); +} + +// List tokens for the user +async function list(client, channel, user) { + await client.setUserContext(user, true); + + const tokenClient = client.newTokenClient(channel, 'localhost:7051'); + + return await tokenClient.list(); +} + +// Create fabric client, channel, orderer, and peer instances. +// These are needed for SDK to invoke token operations. +function createFabricClient() { + // fabric client instance + // starting point for all interactions with the fabric network + const fabric_client = new Fabric_Client(); + + // -- channel instance to represent the ledger + const channel = fabric_client.newChannel(channel_name); + console.log(' Created client side object to represent the channel'); + + // -- peer instance to represent a peer on the channel + const peer = fabric_client.newPeer('grpc://localhost:7051'); + console.log(' Created client side object to represent the peer'); + + // -- orderer instance to reprsent the channel's orderer + const orderer = fabric_client.newOrderer('grpc://localhost:7050') + console.log(' Created client side object to represent the orderer'); + + // add peer and orderer to the channel + channel.addPeer(peer); + channel.addOrderer(orderer); + + return {fabric_client: fabric_client, channel: channel}; +} + +// Create admin, user1 and user2 by loading crypto files +async function createUsers() { + // This sample application will read user idenitity information from + // pre-generated crypto files and create users. It will use a client object as + // an easy way to create the user objects from known cyrpto material. + + const client = new Fabric_Client(); + + // load admin + let keyPath = path.join(__dirname, '../../basic-network/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore'); + let keyPEM = Buffer.from(readAllFiles(keyPath)[0]).toString(); + let certPath = path.join(__dirname, '../../basic-network/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts'); + let certPEM = readAllFiles(certPath)[0]; + + let user_opts = { + username: 'admin', + mspid: 'Org1MSP', + skipPersistence: true, + cryptoContent: { + privateKeyPEM: keyPEM, + signedCertPEM: certPEM + } + }; + const admin = await client.createUser(user_opts); + + // load user1 + keyPath = path.join(__dirname, '../../basic-network/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore'); + keyPEM = Buffer.from(readAllFiles(keyPath)[0]).toString(); + certPath = path.join(__dirname, '../../basic-network/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts'); + certPEM = readAllFiles(certPath)[0]; + + user_opts = { + username: 'user1', + mspid: 'Org1MSP', + skipPersistence: true, + cryptoContent: { + privateKeyPEM: keyPEM, + signedCertPEM: certPEM + } + }; + const user1 = await client.createUser(user_opts); + + // load user2 + keyPath = path.join(__dirname, '../../basic-network/crypto-config/peerOrganizations/org1.example.com/users/User2@org1.example.com/msp/keystore'); + keyPEM = Buffer.from(readAllFiles(keyPath)[0]).toString(); + certPath = path.join(__dirname, '../../basic-network/crypto-config/peerOrganizations/org1.example.com/users/User2@org1.example.com/msp/signcerts'); + certPEM = readAllFiles(certPath)[0]; + + user_opts = { + username: 'user2', + mspid: 'Org1MSP', + skipPersistence: true, + cryptoContent: { + privateKeyPEM: keyPEM, + signedCertPEM: certPEM + } + }; + const user2 = await client.createUser(user_opts); + + return {admin: admin, user1: user1, user2: user2}; +} + +function readAllFiles(dir) { + const files = fs.readdirSync(dir); + const certs = []; + files.forEach((file_name) => { + const file_path = path.join(dir, file_name); + const data = fs.readFileSync(file_path); + certs.push(data); + }); + return certs; +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} diff --git a/fabtoken/javascript/package.json b/fabtoken/javascript/package.json new file mode 100644 index 0000000000..433febde5e --- /dev/null +++ b/fabtoken/javascript/package.json @@ -0,0 +1,22 @@ +{ + "name": "fabtoken", + "version": "1.0.0", + "description": "Hyperledger Fabric Token Sample Application", + "main": "fabtoken.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "fabric-client": "unstable", + "fs-extra": "^6.0.1", + "util": "^0.10.3" + }, + "license": "Apache-2.0", + "keywords": [ + "Hyperledger", + "Fabric", + "Token", + "Sample", + "Application" + ] +} diff --git a/fabtoken/startFabric.sh b/fabtoken/startFabric.sh new file mode 100755 index 0000000000..e2ccd72c7d --- /dev/null +++ b/fabtoken/startFabric.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# +# Copyright IBM Corp All Rights Reserved +# +# SPDX-License-Identifier: Apache-2.0 +# +# Exit on first error +set -e + +# don't rewrite paths for Windows Git Bash users +export MSYS_NO_PATHCONV=1 +starttime=$(date +%s) + +# launch network; create channel and join peer to channel +cd ../basic-network +./start.sh + +cat < + - example 1: node fabtoken issue user1 USD 100 + node fabtoken list + - example: node fabtoken list user1 + - select a token to transfer or redeem and pass "tx_id" and "index" as input parameters + node fabtoken transfer + - example: node fabtoken transfer user1 user2 30 c9b1211d9ad809e6ee1b542de6886d8d1d9e1c938d88eff23a3ddb4e8c080e4d 0 + - and are the "tx_id" and "index" returned from the list operation that specifies the token id for transfer + node fabtoken redeem + - example: node fabtoken redeem user2 10 477c7bf2002814497c228fd8cbc4d80c8b7f1602b2c17ffadb6cf7e5783fa47a 0 + - and are the "tx_id" and "index" returned from the list operation + +EOF