{"meta":{"version":1,"warehouse":"3.0.2"},"models":{"Asset":[{"_id":"themes/subspace/source/css/application.scss","path":"css/application.scss","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/accounting-calculator.svg","path":"icons/accounting-calculator.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/book-address.svg","path":"icons/book-address.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/contactless-payment.svg","path":"icons/contactless-payment.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/close.svg","path":"icons/close.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/cloud-lock.svg","path":"icons/cloud-lock.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/crypto-currency-bitcoin-give.svg","path":"icons/crypto-currency-bitcoin-give.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/crypto-currency-bitcoin-lock.svg","path":"icons/crypto-currency-bitcoin-lock.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/facebook.svg","path":"icons/facebook.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/gauge-dashboard-1-alternate.svg","path":"icons/gauge-dashboard-1-alternate.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/credit-card-1.svg","path":"icons/credit-card-1.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/gesture-tap-2.svg","path":"icons/gesture-tap-2.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/navigation-menu.svg","path":"icons/navigation-menu.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/half-circles.svg","path":"icons/half-circles.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/twitter.svg","path":"icons/twitter.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/icons/github.svg","path":"icons/github.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/images/embark-logo.svg","path":"images/embark-logo.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/images/favicon.png","path":"images/favicon.png","modified":0,"renderable":1},{"_id":"themes/subspace/source/images/circles.png","path":"images/circles.png","modified":0,"renderable":1},{"_id":"themes/subspace/source/images/logo.svg","path":"images/logo.svg","modified":0,"renderable":1},{"_id":"themes/subspace/source/javascripts/scripts.js","path":"javascripts/scripts.js","modified":0,"renderable":1},{"_id":"themes/subspace/source/fonts/LICENSE.txt","path":"fonts/LICENSE.txt","modified":0,"renderable":1},{"_id":"themes/subspace/source/javascripts/jquery.js","path":"javascripts/jquery.js","modified":0,"renderable":1},{"_id":"themes/subspace/source/css/utilities/text-color.scss","path":"css/utilities/text-color.scss","modified":0,"renderable":1},{"_id":"themes/subspace/source/css/utilities/text-size.scss","path":"css/utilities/text-size.scss","modified":0,"renderable":1},{"_id":"themes/subspace/source/fonts/Roboto-Regular.ttf","path":"fonts/Roboto-Regular.ttf","modified":0,"renderable":1},{"_id":"themes/subspace/source/fonts/Roboto-Bold.ttf","path":"fonts/Roboto-Bold.ttf","modified":0,"renderable":1},{"_id":"src/CNAME","path":"CNAME","modified":1,"renderable":0},{"_id":"src/d1.png","path":"d1.png","modified":0,"renderable":0},{"_id":"src/d2.png","path":"d2.png","modified":0,"renderable":0},{"_id":"src/d4.png","path":"d4.png","modified":0,"renderable":0},{"_id":"src/d3.png","path":"d3.png","modified":0,"renderable":0}],"Cache":[{"_id":"source/packages/docs/integrations.md","hash":"da39a3ee5e6b4b0d3255bfef95601890afd80709","modified":1584642530738},{"_id":"source/packages/docs/CNAME","hash":"545c29d0214716b4d25f490ba92f7fc192267fa9","modified":1584642530729},{"_id":"source/packages/docs/api.md","hash":"ffde01913e2d96ec29385876a11263bc11fabceb","modified":1584723815864},{"_id":"source/packages/docs/apollo-client.md","hash":"cc160f89cce8517911336cee5b25cdf65917deff","modified":1584724636348},{"_id":"source/packages/docs/getting-started.md","hash":"1042b4f008235f64d7ea68bb2c2bda1ead199e55","modified":1584725507304},{"_id":"source/packages/docs/how-it-works.md","hash":"d63d4586abfc27e244c79fadc6bd511177000f93","modified":1584642530738},{"_id":"source/packages/docs/react.md","hash":"9535a6f86f81f41f7fc15abda4fa0b643445ca01","modified":1584724578524},{"_id":"source/packages/docs/index.md","hash":"c7f9f58861a13ea8feab62f9b1bdd77dbda50332","modified":1584642530738},{"_id":"source/packages/docs/reactive-graphql.md","hash":"94b59d798d01c251bdb6a6f68d7ea3488fedb100","modified":1584724721872},{"_id":"source/packages/docs/integrations-overview.md","hash":"27810364f74ec73eb6dad0c8de9d72d9b8b51819","modified":1584642530738},{"_id":"source/packages/docs/redux.md","hash":"a1e05949d04ccb860ecdf1dbbc0c0910eb08dc3a","modified":1584725185944},{"_id":"source/packages/docs/redux-observable.md","hash":"704cffb8c78cdd277e113bc36341b7ea3dc39e76","modified":1584725239448},{"_id":"source/packages/docs/tutorial.md","hash":"baadad45a3c4192b5b32636c0552e32f00e75076","modified":1584642530739},{"_id":"source/packages/docs/readme.md","hash":"87edb67efc2d3ff34adb4cdaf253af19602ad901","modified":1584642530739},{"_id":"source/packages/docs/vue.md","hash":"eae7d13431ff55dcb68392cf78abef0cdb7b4e4e","modified":1584724932780},{"_id":"source/packages/docs/d1.png","hash":"615cbb4801559261ddeaedcda06679dd13ff8f26","modified":1584642530731},{"_id":"themes/subspace/languages/en.yml","hash":"52b19a8059904b1c4d7024cb5f2e7a8071b0ab57","modified":1584642530754},{"_id":"themes/subspace/languages/.DS_Store","hash":"d76a2d7ac0d577c6351a42a52cde25f60bbe2e26","modified":1584642530754},{"_id":"themes/subspace/layout/about-us.ejs","hash":"906fffa56cbe7211846775e0b4844db1825230a7","modified":1584642530754},{"_id":"themes/subspace/layout/layout.ejs","hash":"38e9ef77f035f17fb6c4a46d3f2204d2c5f51459","modified":1584642530755},{"_id":"themes/subspace/layout/index.ejs","hash":"b104f0b2eaae87f8c90a6cdafc18e2f5c699d02a","modified":1584642530755},{"_id":"themes/subspace/layout/page.ejs","hash":"e006206018c66a2c2937215010c9c3cd705f05ad","modified":1584642530755},{"_id":"themes/subspace/layout/404.ejs","hash":"52679437dd54e18877ce4d3d6d9989b3515ff6bf","modified":1584642530754},{"_id":"source/packages/docs/d2.png","hash":"87c8eaa980edbe019c706ab8463148227bd25192","modified":1584642530733},{"_id":"source/packages/docs/d4.png","hash":"5cc6b8b2d141e41719bf9ace46d2be80d8236a69","modified":1584642530737},{"_id":"source/packages/docs/d3.png","hash":"ca19a6ae5bf461a92af770d0ed7e82170c27da4a","modified":1584642530735},{"_id":"themes/subspace/layout/partial/head.ejs","hash":"7b788f51a2f5cc3891dd2bc9db8590580a6c47da","modified":1584642530755},{"_id":"themes/subspace/source/css/application.scss","hash":"424452ff93d472ea4097d60437a5c2fb8be74e49","modified":1584642530756},{"_id":"themes/subspace/layout/partial/header.ejs","hash":"a6cbefc575f7d67895463ffce2813eee00cdc1d0","modified":1584642530756},{"_id":"themes/subspace/source/css/.DS_Store","hash":"6d11537ea7ac951519f8387948002c67d58ac9ef","modified":1584642530756},{"_id":"themes/subspace/layout/partial/footer.ejs","hash":"e3b2e69a69c69156fc930faa17555c00ced0a76f","modified":1584726996412},{"_id":"themes/subspace/layout/partial/mailpopup.ejs","hash":"edb6ea1449c8f419c1f6cd3fa0f3f6bac709675d","modified":1584642530756},{"_id":"themes/subspace/layout/partial/header-short.ejs","hash":"fcf71590daa7f07a0d898a9cd432faebb7b06ea9","modified":1584642530755},{"_id":"themes/subspace/source/icons/accounting-calculator.svg","hash":"7891b12b3fd13594b3ae17c10b3523208265be33","modified":1584642530762},{"_id":"themes/subspace/source/icons/book-address.svg","hash":"e2635b49f36833ec0f3373d53e3c5819a3446c24","modified":1584642530762},{"_id":"themes/subspace/source/icons/contactless-payment.svg","hash":"6674a6a6c06539d7be7bcb2459448d4895541051","modified":1584642530763},{"_id":"themes/subspace/source/icons/close.svg","hash":"07c332a892c2b2a107bf53a055425064006b7161","modified":1584642530763},{"_id":"themes/subspace/source/icons/cloud-lock.svg","hash":"b76429f6da1aaa8a1f1d1e72ae7167899976e7aa","modified":1584642530763},{"_id":"themes/subspace/source/icons/crypto-currency-bitcoin-give.svg","hash":"a07cb23aa2c7b81167289cfee51450e110dd46d2","modified":1584642530763},{"_id":"themes/subspace/source/icons/crypto-currency-bitcoin-lock.svg","hash":"27219b9e068b7dc5323217e3f49bad16f6555470","modified":1584642530763},{"_id":"themes/subspace/source/icons/facebook.svg","hash":"0cf65e7228226ff7aa72c74d35368db7599c884b","modified":1584642530763},{"_id":"themes/subspace/source/icons/gauge-dashboard-1-alternate.svg","hash":"4ab202003ecb28775848f4b6fa61a45b4cdc8a7c","modified":1584642530763},{"_id":"themes/subspace/source/icons/credit-card-1.svg","hash":"2c6082035b515eff854f84905cca61a53275aa8e","modified":1584642530763},{"_id":"themes/subspace/source/icons/gesture-tap-2.svg","hash":"8e9a60be2d1080c184f8863d8059472ae1051432","modified":1584642530763},{"_id":"themes/subspace/source/icons/navigation-menu.svg","hash":"d6b4d9e2da8849ac362bcb8d634725b921ebf46c","modified":1584642530764},{"_id":"themes/subspace/source/icons/half-circles.svg","hash":"0be6efb2cd315348a5f2f1404da205d11d5e78c3","modified":1584642530764},{"_id":"themes/subspace/source/icons/twitter.svg","hash":"dab32630d9eb04c293f9c4775271953d57eb8642","modified":1584642530764},{"_id":"themes/subspace/source/images/.DS_Store","hash":"df2fbeb1400acda0909a32c1cf6bf492f1121e07","modified":1584642530764},{"_id":"themes/subspace/source/icons/github.svg","hash":"4ad3447484a193da8e10d9705ebb598de10873e6","modified":1584642530764},{"_id":"themes/subspace/source/images/embark-logo.svg","hash":"682af62e01cd85c11235bd2258b8f87ee9b44afb","modified":1584642530765},{"_id":"themes/subspace/source/images/favicon.png","hash":"7f0c4305cd9711e9dd20ac94a5559f4d67a9fe9b","modified":1584642530765},{"_id":"themes/subspace/source/images/circles.png","hash":"ca3ed456a67c9c329e638990b9a8805a2f33c68c","modified":1584642530764},{"_id":"themes/subspace/source/images/logo.svg","hash":"e7d3c651b56c2bb890b567a67dd860fc96ad6579","modified":1584642530765},{"_id":"themes/subspace/source/javascripts/scripts.js","hash":"1f0ee8c12b179a9607cb1848710254652a7332b2","modified":1584642530766},{"_id":"themes/subspace/source/css/components/_footer.scss","hash":"da39a3ee5e6b4b0d3255bfef95601890afd80709","modified":1584642530756},{"_id":"themes/subspace/source/fonts/LICENSE.txt","hash":"47b573e3824cd5e02a1a3ae99e2735b49e0256e4","modified":1584642530761},{"_id":"themes/subspace/source/javascripts/jquery.js","hash":"9592732de681f4365e9b7016dc5cf76e2a55ee9b","modified":1584642530765},{"_id":"themes/subspace/source/css/components/_accentbox.scss","hash":"d29f556f2e24edec1332ccc00e86426015de422a","modified":1584642530756},{"_id":"themes/subspace/source/css/components/_button.scss","hash":"0888de9a954f5bfb3b84bbfdeed13372d250f69d","modified":1584642530756},{"_id":"themes/subspace/source/css/components/_ghostbox.scss","hash":"1105139aba422228bc5a64863d28d8405d700d15","modified":1584642530757},{"_id":"themes/subspace/source/css/components/_header.scss","hash":"e83d6db7e7c928f4d7d5fd2d57643fe1a551cffc","modified":1584642530757},{"_id":"themes/subspace/source/css/components/_icons.scss","hash":"cb036eb40ed0c2525ca1918e59d73d6a592272ba","modified":1584642530757},{"_id":"themes/subspace/source/css/components/_logo.scss","hash":"c3734c435890094b95a9f340337dbfc682bc1e1a","modified":1584642530757},{"_id":"themes/subspace/source/css/components/_notification.scss","hash":"87e1aae9e3097c59833d34e72f7ae8414f667cdb","modified":1584724117876},{"_id":"themes/subspace/source/css/components/_prism-highlighting.scss","hash":"f8471f2667d53a43cb3b6003d7ecafc811283c24","modified":1584642530757},{"_id":"themes/subspace/source/css/components/_spotlightbox.scss","hash":"2a38ea8c753890d1683b435e33e8377fb17671dc","modified":1584642530758},{"_id":"themes/subspace/source/css/components/_teaser.scss","hash":"c653e382a5a7074f5f86b89c6640a56e083b8471","modified":1584642530758},{"_id":"themes/subspace/source/css/components/_user.scss","hash":"b3663cc7b8c0afdf5f8a69031da7ac3365e844a9","modified":1584642530758},{"_id":"themes/subspace/source/css/components/_whisperbox.scss","hash":"3852852e8644c71b6604915342a10abbfd681d4b","modified":1584642530758},{"_id":"themes/subspace/source/css/objects/_actionbar.scss","hash":"195c09931d611dbfcaf9822413504ebb0e77076b","modified":1584642530758},{"_id":"themes/subspace/source/css/components/_popup.scss","hash":"ccacb82e0b24378a96841d7a480e6530630d882d","modified":1584642530757},{"_id":"themes/subspace/source/css/objects/_content.scss","hash":"4ef659a6c260c847813fabb8718f990c5c126792","modified":1584642530758},{"_id":"themes/subspace/source/css/objects/_distance.scss","hash":"7ad96acdb01f35bbd8941d61f59b055568c6e545","modified":1584642530758},{"_id":"themes/subspace/source/css/objects/_heading.scss","hash":"6569220491e75b751070f2b589fea348745e27d1","modified":1584642530759},{"_id":"themes/subspace/source/css/objects/_list.scss","hash":"ed672664d7e4a2f041ce3e3dcc6b3c9f3ebbfa7e","modified":1584642530759},{"_id":"themes/subspace/source/css/objects/_media.scss","hash":"be25ab7a3c1e3bccfaaa5fdbeeccf9308694921d","modified":1584642530759},{"_id":"themes/subspace/source/css/objects/_navigation.scss","hash":"64e3b33422ccccf702e630125a9b3ca52999c2d6","modified":1584642530759},{"_id":"themes/subspace/source/css/objects/_grid.scss","hash":"4dc75ce1b1d58e4ca6624db8115ffd196c996733","modified":1584642530759},{"_id":"themes/subspace/source/css/objects/_side-navigation.scss","hash":"231d32b8a6137f3bd4d4ac2aa24476620f558f61","modified":1584642530759},{"_id":"themes/subspace/source/css/settings/_config.scss","hash":"a27b23a9d0e1a77c4361c4cb1f7ad0468906bbb3","modified":1584642530759},{"_id":"themes/subspace/source/css/settings/_preset.scss","hash":"36ed217633bf1a18f5221299e0f60277271eecb4","modified":1584642530759},{"_id":"themes/subspace/source/css/objects/.DS_Store","hash":"5fcb0ec5a267305e0c44b7fa11ae2793b4f428c6","modified":1584642530758},{"_id":"themes/subspace/source/css/settings/_typography.scss","hash":"6cd55798081c53c48e7ad0324a0799b53fc5ff17","modified":1584642530760},{"_id":"themes/subspace/source/css/tools/_column.scss","hash":"9e79a4e3b4ab33091af14704d35ca2ab4263b741","modified":1584642530760},{"_id":"themes/subspace/source/css/tools/_get-brand-color.scss","hash":"5e773a10bb143a2b423287b7faff79ac5ce3fcac","modified":1584642530760},{"_id":"themes/subspace/source/css/tools/_get-color.scss","hash":"c7b667b92df7fa246be2f32f395ebbde23ea0355","modified":1584642530760},{"_id":"themes/subspace/source/css/tools/_get-font.scss","hash":"b2895a7ef8c26ba6b34c34f1f2fb2ea5449f89cd","modified":1584642530760},{"_id":"themes/subspace/source/css/tools/_get-index.scss","hash":"72281c30f7e3db2060898d3c9b250bc6c8829216","modified":1584642530760},{"_id":"themes/subspace/source/css/tools/_get-layout-color.scss","hash":"8e57145b98219bc874041197bde973193e2ff7a8","modified":1584642530760},{"_id":"themes/subspace/source/css/tools/_get-notification-color.scss","hash":"761e671fe7cbd70c190254818c8e55e6d3d7ee90","modified":1584642530760},{"_id":"themes/subspace/source/css/tools/_get-semantic-color.scss","hash":"316f7baaa9aeac4f891901882479b37230b5c591","modified":1584642530761},{"_id":"themes/subspace/source/css/tools/_get-spacing.scss","hash":"fc5b46752f7e37da64ddad94392450d7d4f40a87","modified":1584642530761},{"_id":"themes/subspace/source/css/tools/_list-reset.scss","hash":"ed3aa5ee39890dd0454c695e1bf0a45f751c5b04","modified":1584642530761},{"_id":"themes/subspace/source/css/tools/_respond.scss","hash":"ba73d16c16235365bea5da78df0672d990ba4579","modified":1584642530761},{"_id":"themes/subspace/source/css/utilities/text-color.scss","hash":"414a963b70ee7a6250a247bdbd03f2e67e5bbdcd","modified":1584642530761},{"_id":"themes/subspace/source/css/utilities/text-size.scss","hash":"93494af1f3f44293fc9357bd726eadedf76d9516","modified":1584642530761},{"_id":"themes/subspace/source/fonts/Roboto-Regular.ttf","hash":"dd1b1db13ff1f72138c134c62f38fef83749f36a","modified":1584642530762},{"_id":"themes/subspace/source/fonts/Roboto-Bold.ttf","hash":"0ce37ced9c5fcac9bdc452a432c1258870ba4677","modified":1584642530762},{"_id":"public/integrations.html","hash":"85774fa53c301de5337727888ae5489a9156d811","modified":1584727082835},{"_id":"public/api.html","hash":"13c3320c5a3e33ecbcbb286191a57465715af25e","modified":1584727082835},{"_id":"public/apollo-client.html","hash":"d0fcff76c4536fde92ca1c3556b2de0c3e3098a5","modified":1584727082835},{"_id":"public/getting-started.html","hash":"2106811f8cb2e125ca84ec1cdc7937c774c18a0d","modified":1584727082835},{"_id":"public/how-it-works.html","hash":"64eb88557275e07bbe4d46ab746fe08a2864c806","modified":1584727082835},{"_id":"public/reactive-graphql.html","hash":"e4837ce34e1ef30752b067a9e8fe4d3f412f2a50","modified":1584727082835},{"_id":"public/react.html","hash":"95407d198a952ac54ec10e2cf12f3ebd1af8f2a5","modified":1584727082835},{"_id":"public/integrations-overview.html","hash":"db13224a82e947617b63adfbfe24205a16f0fb7c","modified":1584727082835},{"_id":"public/redux.html","hash":"59f4dfc76db1cd300fbd17f6e61f54f8b83dd32d","modified":1584727082835},{"_id":"public/tutorial.html","hash":"115a94498eb92e75f89dd7bd885f167e2b841c40","modified":1584727082835},{"_id":"public/redux-observable.html","hash":"0c172bece1884a62b235210155303067d9e06850","modified":1584727082835},{"_id":"public/readme.html","hash":"27d2d8e99da426d91426cefe567102f7b0e7f21e","modified":1584727082835},{"_id":"public/vue.html","hash":"a550dd615bbc7a826905c58c191a95a7313ffa54","modified":1584727082835},{"_id":"public/de/integrations.html","hash":"c729a81651366281d7351fcc1cc018126d5588db","modified":1584727082835},{"_id":"public/de/api.html","hash":"e907e7a1b0cf5bbc1f72d3c0eed2109edf14db5b","modified":1584727082835},{"_id":"public/de/apollo-client.html","hash":"979c7a64b100821848602dda9cfceac1c4474a57","modified":1584727082835},{"_id":"public/de/getting-started.html","hash":"dc75f1ed09f52378ac12374fa6bc5285a56d9abe","modified":1584727082835},{"_id":"public/de/how-it-works.html","hash":"fb9460aef04b26da10bf848dc4cc22101ef02f55","modified":1584727082835},{"_id":"public/de/index.html","hash":"59dc6d6c72a2989e3c7d5867abdb97d721be1fbe","modified":1584727082835},{"_id":"public/de/reactive-graphql.html","hash":"1ada5c419ecb86a69ca16bdbb3ee5a3b6357dea0","modified":1584727082835},{"_id":"public/de/react.html","hash":"c5773bd40756d102040b3c4011966f5ea64bee3c","modified":1584727082835},{"_id":"public/de/integrations-overview.html","hash":"3cc10e74f4065af48c4b5fb63568b35f7e958517","modified":1584727082835},{"_id":"public/de/redux.html","hash":"9a070d4c5faf084222d6a63a4f42422501dd1f8f","modified":1584727082835},{"_id":"public/de/redux-observable.html","hash":"a9c396192fe9aca5da96315c9e55375cb6d06497","modified":1584727082835},{"_id":"public/de/tutorial.html","hash":"b3eb80b81c3b305805f8910d93a11297089861c2","modified":1584727082835},{"_id":"public/de/readme.html","hash":"e0f5220f7ea548f72809783a2ef471fd2c385111","modified":1584727082835},{"_id":"public/de/vue.html","hash":"3b9e2ec5c1c79ef2920d35d352f22033772e4069","modified":1584727082835},{"_id":"public/index.html","hash":"1228366ec5e55286caecb4a4c829c068bf6e36b4","modified":1584727082835},{"_id":"public/CNAME","hash":"b8f0ef58640029c7ab8509ad372f80c1fcf56c5b","modified":1584727177601},{"_id":"public/icons/accounting-calculator.svg","hash":"7891b12b3fd13594b3ae17c10b3523208265be33","modified":1584642922115},{"_id":"public/icons/book-address.svg","hash":"e2635b49f36833ec0f3373d53e3c5819a3446c24","modified":1584642922115},{"_id":"public/icons/contactless-payment.svg","hash":"6674a6a6c06539d7be7bcb2459448d4895541051","modified":1584642922115},{"_id":"public/icons/close.svg","hash":"07c332a892c2b2a107bf53a055425064006b7161","modified":1584642922115},{"_id":"public/icons/cloud-lock.svg","hash":"b76429f6da1aaa8a1f1d1e72ae7167899976e7aa","modified":1584642922115},{"_id":"public/icons/crypto-currency-bitcoin-give.svg","hash":"a07cb23aa2c7b81167289cfee51450e110dd46d2","modified":1584642922115},{"_id":"public/icons/crypto-currency-bitcoin-lock.svg","hash":"27219b9e068b7dc5323217e3f49bad16f6555470","modified":1584642922115},{"_id":"public/icons/facebook.svg","hash":"0cf65e7228226ff7aa72c74d35368db7599c884b","modified":1584642922115},{"_id":"public/icons/gesture-tap-2.svg","hash":"8e9a60be2d1080c184f8863d8059472ae1051432","modified":1584642922115},{"_id":"public/icons/gauge-dashboard-1-alternate.svg","hash":"4ab202003ecb28775848f4b6fa61a45b4cdc8a7c","modified":1584642922115},{"_id":"public/icons/credit-card-1.svg","hash":"2c6082035b515eff854f84905cca61a53275aa8e","modified":1584642922115},{"_id":"public/icons/navigation-menu.svg","hash":"d6b4d9e2da8849ac362bcb8d634725b921ebf46c","modified":1584642922115},{"_id":"public/icons/half-circles.svg","hash":"0be6efb2cd315348a5f2f1404da205d11d5e78c3","modified":1584642922115},{"_id":"public/icons/twitter.svg","hash":"dab32630d9eb04c293f9c4775271953d57eb8642","modified":1584642922115},{"_id":"public/icons/github.svg","hash":"4ad3447484a193da8e10d9705ebb598de10873e6","modified":1584642922115},{"_id":"public/images/embark-logo.svg","hash":"682af62e01cd85c11235bd2258b8f87ee9b44afb","modified":1584642922115},{"_id":"public/images/favicon.png","hash":"7f0c4305cd9711e9dd20ac94a5559f4d67a9fe9b","modified":1584642922115},{"_id":"public/images/logo.svg","hash":"e7d3c651b56c2bb890b567a67dd860fc96ad6579","modified":1584642922115},{"_id":"public/images/circles.png","hash":"ca3ed456a67c9c329e638990b9a8805a2f33c68c","modified":1584642922115},{"_id":"public/fonts/LICENSE.txt","hash":"47b573e3824cd5e02a1a3ae99e2735b49e0256e4","modified":1584642922115},{"_id":"public/javascripts/scripts.js","hash":"1f0ee8c12b179a9607cb1848710254652a7332b2","modified":1584642922115},{"_id":"public/css/utilities/text-size.css","hash":"6340d74612d186555e471c57175a5f0a3f661cdc","modified":1584642922115},{"_id":"public/css/utilities/text-color.css","hash":"3d763e25e9078d5375b4fd0d9b100aa1b8a91d93","modified":1584642922115},{"_id":"public/javascripts/jquery.js","hash":"9592732de681f4365e9b7016dc5cf76e2a55ee9b","modified":1584642922115},{"_id":"public/css/application.css","hash":"e7e477ef203d3d902df2518d63ef12731d0a50f8","modified":1584642922115},{"_id":"public/d1.png","hash":"615cbb4801559261ddeaedcda06679dd13ff8f26","modified":1584642922115},{"_id":"public/fonts/Roboto-Bold.ttf","hash":"0ce37ced9c5fcac9bdc452a432c1258870ba4677","modified":1584642922115},{"_id":"public/d4.png","hash":"5cc6b8b2d141e41719bf9ace46d2be80d8236a69","modified":1584642922115},{"_id":"public/d3.png","hash":"ca19a6ae5bf461a92af770d0ed7e82170c27da4a","modified":1584642922115},{"_id":"public/fonts/Roboto-Regular.ttf","hash":"dd1b1db13ff1f72138c134c62f38fef83749f36a","modified":1584642922115},{"_id":"public/d2.png","hash":"87c8eaa980edbe019c706ab8463148227bd25192","modified":1584642922115},{"_id":"source/packages/docs/_data/sidebar.yml","hash":"09bc83728b4dc7a9db44fdfce15769972c1eec3a","modified":1584642530730},{"_id":"source/packages/docs/_posts/hello-world.md","hash":"7d98d6592de80fdcd2949bd7401cec12afd98cdf","modified":1584642530730},{"_id":"source/packages/docs/.DS_Store","hash":"078fc163bb0c365b31f211f49a5e78e2703b3f12","modified":1584643079469},{"_id":"themes/subspace/layout/partial/analytics.ejs","hash":"bf68e467c9adc5e07591ddf1e74da824750948f2","modified":1584726372396},{"_id":"src/integrations.md","hash":"da39a3ee5e6b4b0d3255bfef95601890afd80709","modified":1584723381904},{"_id":"src/CNAME","hash":"b8f0ef58640029c7ab8509ad372f80c1fcf56c5b","modified":1584727154424},{"_id":"src/api.md","hash":"ffde01913e2d96ec29385876a11263bc11fabceb","modified":1584723815864},{"_id":"src/apollo-client.md","hash":"cc160f89cce8517911336cee5b25cdf65917deff","modified":1584724636348},{"_id":"src/getting-started.md","hash":"1042b4f008235f64d7ea68bb2c2bda1ead199e55","modified":1584725507304},{"_id":"src/how-it-works.md","hash":"d63d4586abfc27e244c79fadc6bd511177000f93","modified":1583767514948},{"_id":"src/index.md","hash":"c7f9f58861a13ea8feab62f9b1bdd77dbda50332","modified":1584723381904},{"_id":"src/integrations-overview.md","hash":"27810364f74ec73eb6dad0c8de9d72d9b8b51819","modified":1583767514948},{"_id":"src/react.md","hash":"9535a6f86f81f41f7fc15abda4fa0b643445ca01","modified":1584724578524},{"_id":"src/reactive-graphql.md","hash":"94b59d798d01c251bdb6a6f68d7ea3488fedb100","modified":1584724721872},{"_id":"src/readme.md","hash":"87edb67efc2d3ff34adb4cdaf253af19602ad901","modified":1583767514948},{"_id":"src/redux-observable.md","hash":"704cffb8c78cdd277e113bc36341b7ea3dc39e76","modified":1584725239448},{"_id":"src/tutorial.md","hash":"baadad45a3c4192b5b32636c0552e32f00e75076","modified":1583767514948},{"_id":"src/redux.md","hash":"a1e05949d04ccb860ecdf1dbbc0c0910eb08dc3a","modified":1584725185944},{"_id":"src/vue.md","hash":"eae7d13431ff55dcb68392cf78abef0cdb7b4e4e","modified":1584724932780},{"_id":"src/_data/sidebar.yml","hash":"09bc83728b4dc7a9db44fdfce15769972c1eec3a","modified":1584723381896},{"_id":"src/_posts/hello-world.md","hash":"7d98d6592de80fdcd2949bd7401cec12afd98cdf","modified":1584723381896},{"_id":"src/d1.png","hash":"615cbb4801559261ddeaedcda06679dd13ff8f26","modified":1584723381896},{"_id":"src/d2.png","hash":"87c8eaa980edbe019c706ab8463148227bd25192","modified":1584723381900},{"_id":"src/d4.png","hash":"5cc6b8b2d141e41719bf9ace46d2be80d8236a69","modified":1584723381904},{"_id":"src/d3.png","hash":"ca19a6ae5bf461a92af770d0ed7e82170c27da4a","modified":1584723381904},{"_id":"public/2020/03/20/hello-world/index.html","hash":"76e005b778094add25dd3b5e8cd6753cbe5be2db","modified":1584727082835},{"_id":"public/archives/index.html","hash":"b44a7681a1f8002bc500ab99b33ee189a4dff877","modified":1584727082835},{"_id":"public/archives/2020/index.html","hash":"3ea6c369354a2c647460952d3be164d4dfb51964","modified":1584727082835},{"_id":"public/archives/2020/03/index.html","hash":"5b2dc0b08bb39bb125e179986bf5b4d8c508d984","modified":1584727082835},{"_id":"public/de/2020/03/20/hello-world/index.html","hash":"2ac3198cb1a67347580617ca9521f24739b2a5aa","modified":1584727082835}],"Category":[],"Data":[{"_id":"sidebar","data":{"docs":[{"title":"How it works","path":"how-it-works.html"},{"title":"Getting Started","path":"getting-started.html","children":[{"title":"Installation","path":"#Installation"},{"title":"Importing the library","path":"#Importing-the-library"},{"title":"Connecting to a web3 provider","path":"#Connecting-to-a-web3-provider"},{"title":"Enhancing your contract objects","path":"#Enhancing-your-contract-objects"},{"title":"Reacting to data","path":"#Reacting-to-data"},{"title":"Tracking state","path":"#Tracking-state"},{"title":"Tracking events","path":"#Tracking-events"},{"title":"Tracking balances","path":"#Tracking-balances"},{"title":"Getting block data, gas prices and block time","path":"#Getting-block-data-gas-prices-and-block-time"},{"title":"Subscriptions","path":"#Subscriptions"},{"title":"Cleanup","path":"#Cleanup"}]},{"title":"Integrations","path":"empty","children":[{"title":"Overview","path":"integrations-overview.html"},{"title":"React","path":"react.html"},{"title":"Vue","path":"vue.html"},{"title":"Redux","path":"empty","children":[{"title":"redux","path":"redux.html"},{"title":"redux-observable","path":"redux-observable.html"}]},{"title":"reactive-graphql","path":"reactive-graphql.html"},{"title":"apollo-client","path":"apollo-client.html"}]},{"title":"Tutorial","path":"tutorial.html"},{"title":"API","path":"api.html","children":[{"title":"General","path":"#general"},{"title":"Contract methods","path":"#Contract-methods"},{"title":"Blocks, gas price and block time","path":"#Blocks-gas-price-and-block-time"},{"title":"Low level API for data tracking","path":"#Low-level-API-for-data-tracking"}]}]}}],"Page":[{"_content":"","source":"integrations.md","raw":"","date":"2020-03-20T16:56:21.904Z","updated":"2020-03-20T16:56:21.904Z","path":"integrations.html","title":"","comments":1,"layout":"page","_id":"ck80hf5kd0000m4jeh9dp1k1h","content":"","site":{"data":{"sidebar":{"docs":[{"title":"How it works","path":"how-it-works.html"},{"title":"Getting Started","path":"getting-started.html","children":[{"title":"Installation","path":"#Installation"},{"title":"Importing the library","path":"#Importing-the-library"},{"title":"Connecting to a web3 provider","path":"#Connecting-to-a-web3-provider"},{"title":"Enhancing your contract objects","path":"#Enhancing-your-contract-objects"},{"title":"Reacting to data","path":"#Reacting-to-data"},{"title":"Tracking state","path":"#Tracking-state"},{"title":"Tracking events","path":"#Tracking-events"},{"title":"Tracking balances","path":"#Tracking-balances"},{"title":"Getting block data, gas prices and block time","path":"#Getting-block-data-gas-prices-and-block-time"},{"title":"Subscriptions","path":"#Subscriptions"},{"title":"Cleanup","path":"#Cleanup"}]},{"title":"Integrations","path":"empty","children":[{"title":"Overview","path":"integrations-overview.html"},{"title":"React","path":"react.html"},{"title":"Vue","path":"vue.html"},{"title":"Redux","path":"empty","children":[{"title":"redux","path":"redux.html"},{"title":"redux-observable","path":"redux-observable.html"}]},{"title":"reactive-graphql","path":"reactive-graphql.html"},{"title":"apollo-client","path":"apollo-client.html"}]},{"title":"Tutorial","path":"tutorial.html"},{"title":"API","path":"api.html","children":[{"title":"General","path":"#general"},{"title":"Contract methods","path":"#Contract-methods"},{"title":"Blocks, gas price and block time","path":"#Blocks-gas-price-and-block-time"},{"title":"Low level API for data tracking","path":"#Low-level-API-for-data-tracking"}]}]}}},"excerpt":"","more":""},{"title":"Homepage","_content":"","source":"index.md","raw":"title: Homepage\n---\n","date":"2020-03-20T16:56:21.904Z","updated":"2020-03-20T16:56:21.904Z","path":"index.html","comments":1,"layout":"page","_id":"ck80hf5kp0001m4je0wxk5zrk","content":"","site":{"data":{"sidebar":{"docs":[{"title":"How it works","path":"how-it-works.html"},{"title":"Getting Started","path":"getting-started.html","children":[{"title":"Installation","path":"#Installation"},{"title":"Importing the library","path":"#Importing-the-library"},{"title":"Connecting to a web3 provider","path":"#Connecting-to-a-web3-provider"},{"title":"Enhancing your contract objects","path":"#Enhancing-your-contract-objects"},{"title":"Reacting to data","path":"#Reacting-to-data"},{"title":"Tracking state","path":"#Tracking-state"},{"title":"Tracking events","path":"#Tracking-events"},{"title":"Tracking balances","path":"#Tracking-balances"},{"title":"Getting block data, gas prices and block time","path":"#Getting-block-data-gas-prices-and-block-time"},{"title":"Subscriptions","path":"#Subscriptions"},{"title":"Cleanup","path":"#Cleanup"}]},{"title":"Integrations","path":"empty","children":[{"title":"Overview","path":"integrations-overview.html"},{"title":"React","path":"react.html"},{"title":"Vue","path":"vue.html"},{"title":"Redux","path":"empty","children":[{"title":"redux","path":"redux.html"},{"title":"redux-observable","path":"redux-observable.html"}]},{"title":"reactive-graphql","path":"reactive-graphql.html"},{"title":"apollo-client","path":"apollo-client.html"}]},{"title":"Tutorial","path":"tutorial.html"},{"title":"API","path":"api.html","children":[{"title":"General","path":"#general"},{"title":"Contract methods","path":"#Contract-methods"},{"title":"Blocks, gas price and block time","path":"#Blocks-gas-price-and-block-time"},{"title":"Low level API for data tracking","path":"#Low-level-API-for-data-tracking"}]}]}}},"excerpt":"","more":""},{"_content":"# How it works?\n\n### Setup\n![First Usage - Setup](./d1.png)\n1. A ÐApp requests `Subspace` to track an event, property, or balance.\n2. `Subspace` creates a observable for that event, and a web3 subscription to retrieve events from the chain\n3. The ÐApp subscribes to the observable to receive events.\n\n### Receiving events\n![First Usage - Receiving events](./d2.png)\nDepending on the filter parameters used to track the events, once an event is found, it is stored in `localStorage` and it is also pushed to the observable which delivers it to the ÐApp subscription.\n\n### Tracking already known events \nAfter restarting the ÐApp, either by executing it again in case of a console application or refreshing the browser the behavior of `Subspace` will change: \n![Second Usage - Setup](./d3.png)\n1. The Dapp will request `Subspace` to track an event it already knows, creating an observable and subscription for that event\n2. It will retrieve events that were previously stored in localStorage and deliver them to the DApp subscription, avoiding having to query the chain for the old events again. \n\n![Second Usage - Receiving events](./d4.png)\nThe web3 subscription created previously will start from the last known block instead of beginning from scratch. New events will be delivered normally from this step\n","source":"how-it-works.md","raw":"# How it works?\n\n### Setup\n![First Usage - Setup](./d1.png)\n1. A ÐApp requests `Subspace` to track an event, property, or balance.\n2. `Subspace` creates a observable for that event, and a web3 subscription to retrieve events from the chain\n3. The ÐApp subscribes to the observable to receive events.\n\n### Receiving events\n![First Usage - Receiving events](./d2.png)\nDepending on the filter parameters used to track the events, once an event is found, it is stored in `localStorage` and it is also pushed to the observable which delivers it to the ÐApp subscription.\n\n### Tracking already known events \nAfter restarting the ÐApp, either by executing it again in case of a console application or refreshing the browser the behavior of `Subspace` will change: \n![Second Usage - Setup](./d3.png)\n1. The Dapp will request `Subspace` to track an event it already knows, creating an observable and subscription for that event\n2. It will retrieve events that were previously stored in localStorage and deliver them to the DApp subscription, avoiding having to query the chain for the old events again. \n\n![Second Usage - Receiving events](./d4.png)\nThe web3 subscription created previously will start from the last known block instead of beginning from scratch. New events will be delivered normally from this step\n","date":"2020-03-20T17:02:58.892Z","updated":"2020-03-09T15:25:14.948Z","path":"how-it-works.html","title":"","comments":1,"layout":"page","_id":"ck80hf5ks0002m4jebndqdftx","content":"
Subspace
to track an event, property, or balance.Subspace
creates a observable for that event, and a web3 subscription to retrieve events from the chain
Depending on the filter parameters used to track the events, once an event is found, it is stored in localStorage
and it is also pushed to the observable which delivers it to the ÐApp subscription.
After restarting the ÐApp, either by executing it again in case of a console application or refreshing the browser the behavior of Subspace
will change:
Subspace
to track an event it already knows, creating an observable and subscription for that event
The web3 subscription created previously will start from the last known block instead of beginning from scratch. New events will be delivered normally from this step
Subspace
to track an event, property, or balance.Subspace
creates a observable for that event, and a web3 subscription to retrieve events from the chain
Depending on the filter parameters used to track the events, once an event is found, it is stored in localStorage
and it is also pushed to the observable which delivers it to the ÐApp subscription.
After restarting the ÐApp, either by executing it again in case of a console application or refreshing the browser the behavior of Subspace
will change:
Subspace
to track an event it already knows, creating an observable and subscription for that event
The web3 subscription created previously will start from the last known block instead of beginning from scratch. New events will be delivered normally from this step
examples/react-apollo
.\nexamples/react-apollo
.\nTo use Subspace with apollo-client
, a ReactiveSchemaLink
from apollo-link-reactive-schema
must be used with a custom schema.
import {InMemoryCache} from "apollo-cache-inmemory";\nimport ApolloClient from "apollo-client";\nimport {ReactiveSchemaLink} from "apollo-link-reactive-schema";\n\nconst schema = makeExecutableSchema({typeDefs, resolvers});\nconst client = new ApolloClient({\n cache: new InMemoryCache(),\n link: new ReactiveSchemaLink({schema)})\n});\n
\n\n\n\nimport { ApolloClient } from "apollo-client";\nimport { InMemoryCache } from "apollo-cache-inmemory";\nimport {ReactiveSchemaLink} from "apollo-link-reactive-schema";\nimport Subspace from "@embarklabs/subspace";\n\n// ...\n\n// Initialize Subspace\nconst subspace = new Subspace(web3);\nawait subspace.init();\n\nconst MyContractInstance = ...; // TODO: obtain a web3.eth.Contract instance\n\nconst typeDefs = `\n type MyEvent {\n someValue: Int\n anotherValue: String\n }\n type Query {\n myEvents: MyEvent!\n }\n`;\n\nconst resolvers = {\n Query: {\n myEvents: () => subspace.trackEvent(MyContractInstance, 'MyEvent', {filter: {}, fromBlock: 1})\n }\n};\n\nconst schema = makeExecutableSchema({ typeDefs, resolvers });\n\nconst client = new ApolloClient({\n cache: new InMemoryCache(),\n link: new ReactiveSchemaLink({schema)})\n});\n
\n\n\n\nexamples/react-apollo
.\nTo use Subspace with apollo-client
, a ReactiveSchemaLink
from apollo-link-reactive-schema
must be used with a custom schema.
import {InMemoryCache} from "apollo-cache-inmemory";\nimport ApolloClient from "apollo-client";\nimport {ReactiveSchemaLink} from "apollo-link-reactive-schema";\n\nconst schema = makeExecutableSchema({typeDefs, resolvers});\nconst client = new ApolloClient({\n cache: new InMemoryCache(),\n link: new ReactiveSchemaLink({schema)})\n});\n
\n\n\n\nimport { ApolloClient } from "apollo-client";\nimport { InMemoryCache } from "apollo-cache-inmemory";\nimport {ReactiveSchemaLink} from "apollo-link-reactive-schema";\nimport Subspace from "@embarklabs/subspace";\n\n// ...\n\n// Initialize Subspace\nconst subspace = new Subspace(web3);\nawait subspace.init();\n\nconst MyContractInstance = ...; // TODO: obtain a web3.eth.Contract instance\n\nconst typeDefs = `\n type MyEvent {\n someValue: Int\n anotherValue: String\n }\n type Query {\n myEvents: MyEvent!\n }\n`;\n\nconst resolvers = {\n Query: {\n myEvents: () => subspace.trackEvent(MyContractInstance, 'MyEvent', {filter: {}, fromBlock: 1})\n }\n};\n\nconst schema = makeExecutableSchema({ typeDefs, resolvers });\n\nconst client = new ApolloClient({\n cache: new InMemoryCache(),\n link: new ReactiveSchemaLink({schema)})\n});\n
\n\n\n\nexamples/react-apollo
.\nNo data
;\n }\n \n returnValue: {eventData.someReturnValue}
\n};\n\n\nconst MyEnhancedComponent = observe(MyComponent);\n\n\nconst SomeOtherComponent = () => {\n const myObservable$ = MyContractInstance.events.MyEvent.track({fromBlock: 1});\n returnMyContractInstance
is a web3.eth.Contract
object pointing to a deployed contract address that has been enhanced with subspace.contract()
. You can use a DApp framework like Embark to easily import that contract instance: import { MyContract } from './embarkArtifacts/contracts';
.\nsubspace-react
, there are full working examples available in Github \nNo data
;\n }\n \n returnValue: {eventData.someReturnValue}
\n};\n\n\nconst MyEnhancedComponent = observe(MyComponent);\n\n\nconst SomeOtherComponent = () => {\n const myObservable$ = MyContractInstance.events.MyEvent.track({fromBlock: 1});\n returnMyContractInstance
is a web3.eth.Contract
object pointing to a deployed contract address that has been enhanced with subspace.contract()
. You can use a DApp framework like Embark to easily import that contract instance: import { MyContract } from './embarkArtifacts/contracts';
.\nsubspace-react
, there are full working examples available in Github \nSubspace also provides a set of components that simplifies its usage within React projects through the @embarklabs/subspace-react
package.
You can install it through npm or yarn:
\n\n\nnpm install --save @embarklabs/subspace-react web3 rxjs # RxJS and Web3.js are needed peer-dependencies\n
\n\n\n\nTo use most of the subspace-react
components, you need to wrap your app with the <SubspaceProvider web3={web3} />
component. This will make Subspace available to any nested components that accesses it via the useSubspace
hook or has been wrapped in the withSubspace
higher order component. Any React component might use Subspace so it makes sense to add the provider near the top level of your dApp. The SubspaceProvider
requires a web3 object
// index.js\nimport React from 'react'\nimport ReactDOM from 'react-dom'\nimport MyApp from './MyApp'\nimport { SubspaceProvider } from '@embarklabs/subspace-react';\n\nconst web3 = new Web3("ws://localhost:8545");\n\nconst rootElement = document.getElementById('root')\nReactDOM.render(\n <SubspaceProvider web3={web3}>\n <MyApp />\n </SubspaceProvider>,\n rootElement\n);\n
\n\n\n\nRather than relying on global variables or passing Subspace through props, The easiest way to access Subspace features is via the useSubspace
hook. Be sure that your entire dApp is wrapped with a <SubspaceProvider />
to have it available througout the component tree.
// index.js\nimport React from 'react'\nimport { useSubspace } from '@embarklabs/subspace-react';\n\nconst MyComponent = () => {\n const subspace = useSubspace();\n\n // do something....\n // subspace.trackBalance(web3.eth.defaultAccount);\n\n return ...;\n}\n\nexport default MyComponent\n
\n\n\n\nThis higher order component is provided as an alternative to the useSubspace
hook. This injects the subspace
property with an already initialized Subspace instance. Just like with the hook, your entire dApp needs to be wrapped with a <SubspaceProvider />
.
// index.js\nimport React from 'react'\nimport { withSubspace } from '@embarklabs/subspace-react';\n\nconst MyComponent = (props) => {\n // do something....\n // props.subspace.trackBalance(web3.eth.defaultAccount);\n\n return ...;\n}\n\nexport default withSubspace(MyComponent);\n
\n\n\n\nUseful to make your component subscribe to any observable props it receives when the component is mounted and automatically unsubscribes when the component is unmounted. It can be used with any kind of observables.
\n\n\nimport { observe } from '@embarklabs/subspace-react';\n\nconst ObserverComponent = observe(WrappedComponent);\n
\n\n\n\nconst MyComponent = ({eventData}) => {\n // Handle initial state when no data is available\n if (!eventData) {\n return <p>No data</p>;\n }\n \n return <p>Value: {eventData.someReturnValue}</p>\n};\n\n\nconst MyEnhancedComponent = observe(MyComponent);\n\n\nconst SomeOtherComponent = () => {\n const myObservable$ = MyContractInstance.events.MyEvent.track({fromBlock: 1});\n return <MyEnhancedComponent myProp={myObservable$} />;\n}\n
\n\n\n\nMyContractInstance
is a web3.eth.Contract
object pointing to a deployed contract address that has been enhanced with subspace.contract()
. You can use a DApp framework like Embark to easily import that contract instance: import { MyContract } from './embarkArtifacts/contracts';
.\nsubspace-react
, there are full working examples available in Github \nSubspace also provides a set of components that simplifies its usage within React projects through the @embarklabs/subspace-react
package.
You can install it through npm or yarn:
\n\n\nnpm install --save @embarklabs/subspace-react web3 rxjs # RxJS and Web3.js are needed peer-dependencies\n
\n\n\n\nTo use most of the subspace-react
components, you need to wrap your app with the <SubspaceProvider web3={web3} />
component. This will make Subspace available to any nested components that accesses it via the useSubspace
hook or has been wrapped in the withSubspace
higher order component. Any React component might use Subspace so it makes sense to add the provider near the top level of your dApp. The SubspaceProvider
requires a web3 object
// index.js\nimport React from 'react'\nimport ReactDOM from 'react-dom'\nimport MyApp from './MyApp'\nimport { SubspaceProvider } from '@embarklabs/subspace-react';\n\nconst web3 = new Web3("ws://localhost:8545");\n\nconst rootElement = document.getElementById('root')\nReactDOM.render(\n <SubspaceProvider web3={web3}>\n <MyApp />\n </SubspaceProvider>,\n rootElement\n);\n
\n\n\n\nRather than relying on global variables or passing Subspace through props, The easiest way to access Subspace features is via the useSubspace
hook. Be sure that your entire dApp is wrapped with a <SubspaceProvider />
to have it available througout the component tree.
// index.js\nimport React from 'react'\nimport { useSubspace } from '@embarklabs/subspace-react';\n\nconst MyComponent = () => {\n const subspace = useSubspace();\n\n // do something....\n // subspace.trackBalance(web3.eth.defaultAccount);\n\n return ...;\n}\n\nexport default MyComponent\n
\n\n\n\nThis higher order component is provided as an alternative to the useSubspace
hook. This injects the subspace
property with an already initialized Subspace instance. Just like with the hook, your entire dApp needs to be wrapped with a <SubspaceProvider />
.
// index.js\nimport React from 'react'\nimport { withSubspace } from '@embarklabs/subspace-react';\n\nconst MyComponent = (props) => {\n // do something....\n // props.subspace.trackBalance(web3.eth.defaultAccount);\n\n return ...;\n}\n\nexport default withSubspace(MyComponent);\n
\n\n\n\nUseful to make your component subscribe to any observable props it receives when the component is mounted and automatically unsubscribes when the component is unmounted. It can be used with any kind of observables.
\n\n\nimport { observe } from '@embarklabs/subspace-react';\n\nconst ObserverComponent = observe(WrappedComponent);\n
\n\n\n\nconst MyComponent = ({eventData}) => {\n // Handle initial state when no data is available\n if (!eventData) {\n return <p>No data</p>;\n }\n \n return <p>Value: {eventData.someReturnValue}</p>\n};\n\n\nconst MyEnhancedComponent = observe(MyComponent);\n\n\nconst SomeOtherComponent = () => {\n const myObservable$ = MyContractInstance.events.MyEvent.track({fromBlock: 1});\n return <MyEnhancedComponent myProp={myObservable$} />;\n}\n
\n\n\n\nMyContractInstance
is a web3.eth.Contract
object pointing to a deployed contract address that has been enhanced with subspace.contract()
. You can use a DApp framework like Embark to easily import that contract instance: import { MyContract } from './embarkArtifacts/contracts';
.\nsubspace-react
, there are full working examples available in Github \nSubspace does not force you to change the architecture of your dApps, making it easy to use on existing projects. In this section you can find some examples and tips on how to integrate Subspace with various frontend frameworks and libraries
\n","site":{"data":{"sidebar":{"docs":[{"title":"How it works","path":"how-it-works.html"},{"title":"Getting Started","path":"getting-started.html","children":[{"title":"Installation","path":"#Installation"},{"title":"Importing the library","path":"#Importing-the-library"},{"title":"Connecting to a web3 provider","path":"#Connecting-to-a-web3-provider"},{"title":"Enhancing your contract objects","path":"#Enhancing-your-contract-objects"},{"title":"Reacting to data","path":"#Reacting-to-data"},{"title":"Tracking state","path":"#Tracking-state"},{"title":"Tracking events","path":"#Tracking-events"},{"title":"Tracking balances","path":"#Tracking-balances"},{"title":"Getting block data, gas prices and block time","path":"#Getting-block-data-gas-prices-and-block-time"},{"title":"Subscriptions","path":"#Subscriptions"},{"title":"Cleanup","path":"#Cleanup"}]},{"title":"Integrations","path":"empty","children":[{"title":"Overview","path":"integrations-overview.html"},{"title":"React","path":"react.html"},{"title":"Vue","path":"vue.html"},{"title":"Redux","path":"empty","children":[{"title":"redux","path":"redux.html"},{"title":"redux-observable","path":"redux-observable.html"}]},{"title":"reactive-graphql","path":"reactive-graphql.html"},{"title":"apollo-client","path":"apollo-client.html"}]},{"title":"Tutorial","path":"tutorial.html"},{"title":"API","path":"api.html","children":[{"title":"General","path":"#general"},{"title":"Contract methods","path":"#Contract-methods"},{"title":"Blocks, gas price and block time","path":"#Blocks-gas-price-and-block-time"},{"title":"Low level API for data tracking","path":"#Low-level-API-for-data-tracking"}]}]}}},"excerpt":"","more":"Subspace does not force you to change the architecture of your dApps, making it easy to use on existing projects. In this section you can find some examples and tips on how to integrate Subspace with various frontend frameworks and libraries
\n"},{"_content":"# reactive-graphql\n\nUsing `reactive-graphql` you can execute GraphQL queries against **Subspace** observables after you create your own type definitions and resolvers.\n\n### Example\n\n\n```js\nconst Subspace = require('@embarklabs/subspace');\nconst MyContract = require('./MyContract');\nconst { pluck } = require('rxjs/operators');\nconst { makeExecutableSchema } = require(\"graphql-tools\");\nconst gql = require(\"graphql-tag\");\nconst { graphql } = require(\"reactive-graphql\");\n\nconst run = async () => {\n const subspace = new Subspace(web3);\n await subspace.init();\n\n const MyContractInstance = ...; // TODO: obtain a web3.eth.contract instance\n\n const typeDefs = `\n type MyEvent {\n someValue: Int\n anotherValue: String\n }\n type Query {\n myEvents: MyEvent!\n }\n `;\n\n const resolvers = {\n Query: {\n myEvents: () => subspace.trackEvent(MyContractInstance, 'MyEvent', { filter: {}, fromBlock: 1 })\n }\n };\n\n const schema = makeExecutableSchema({ typeDefs, resolvers });\n\n const query = gql`\n query {\n myEvents {\n someValue\n anotherValue\n }\n }\n `;\n\n const stream = graphql(schema, query).pipe(pluck('data', 'myEvents'));\n stream.subscribe(data => {\n console.log(data);\n })\n\n}\n\nrun();\n```\n\nUsing reactive-graphql
you can execute GraphQL queries against Subspace observables after you create your own type definitions and resolvers.
const Subspace = require('@embarklabs/subspace');\nconst MyContract = require('./MyContract');\nconst { pluck } = require('rxjs/operators');\nconst { makeExecutableSchema } = require("graphql-tools");\nconst gql = require("graphql-tag");\nconst { graphql } = require("reactive-graphql");\n\nconst run = async () => {\n const subspace = new Subspace(web3);\n await subspace.init();\n\n const MyContractInstance = ...; // TODO: obtain a web3.eth.contract instance\n\n const typeDefs = `\n type MyEvent {\n someValue: Int\n anotherValue: String\n }\n type Query {\n myEvents: MyEvent!\n }\n `;\n\n const resolvers = {\n Query: {\n myEvents: () => subspace.trackEvent(MyContractInstance, 'MyEvent', { filter: {}, fromBlock: 1 })\n }\n };\n\n const schema = makeExecutableSchema({ typeDefs, resolvers });\n\n const query = gql`\n query {\n myEvents {\n someValue\n anotherValue\n }\n }\n `;\n\n const stream = graphql(schema, query).pipe(pluck('data', 'myEvents'));\n stream.subscribe(data => {\n console.log(data);\n })\n\n}\n\nrun();\n
\n\n\n\nUsing reactive-graphql
you can execute GraphQL queries against Subspace observables after you create your own type definitions and resolvers.
const Subspace = require('@embarklabs/subspace');\nconst MyContract = require('./MyContract');\nconst { pluck } = require('rxjs/operators');\nconst { makeExecutableSchema } = require("graphql-tools");\nconst gql = require("graphql-tag");\nconst { graphql } = require("reactive-graphql");\n\nconst run = async () => {\n const subspace = new Subspace(web3);\n await subspace.init();\n\n const MyContractInstance = ...; // TODO: obtain a web3.eth.contract instance\n\n const typeDefs = `\n type MyEvent {\n someValue: Int\n anotherValue: String\n }\n type Query {\n myEvents: MyEvent!\n }\n `;\n\n const resolvers = {\n Query: {\n myEvents: () => subspace.trackEvent(MyContractInstance, 'MyEvent', { filter: {}, fromBlock: 1 })\n }\n };\n\n const schema = makeExecutableSchema({ typeDefs, resolvers });\n\n const query = gql`\n query {\n myEvents {\n someValue\n anotherValue\n }\n }\n `;\n\n const stream = graphql(schema, query).pipe(pluck('data', 'myEvents'));\n stream.subscribe(data => {\n console.log(data);\n })\n\n}\n\nrun();\n
\n\n\n\nredux-observables can be used to manage side effects via Epics
(their core primitive to receive and create stream of actions). Subspace can be configured inside these epics.
It’s recommended to compose these epics by using mergeMap or switchMap operators.
\nHere’s an example on how to use Subspace to subscribe to an Event when the action SOME_ACTION
is dispatched, and then it will trigger myAction
when the observable emits a value.
// ...\n\nconst myEpic = action$ =>\n action$.pipe(\n ofType("SOME_ACTION"), // Execute when the action type is 'INIT'\n switchMap(action =>\n subspace\n .trackEvent(MyContract, "MyEventName", { filter: {}, fromBlock: 1})\n .pipe(\n map(myAction) // Trigger redux action: MY_ACTION with the eventData\n )\n )\n );\n\n// ...\n
\n\n\n\nredux-observables can be used to manage side effects via Epics
(their core primitive to receive and create stream of actions). Subspace can be configured inside these epics.
It’s recommended to compose these epics by using mergeMap or switchMap operators.
\nHere’s an example on how to use Subspace to subscribe to an Event when the action SOME_ACTION
is dispatched, and then it will trigger myAction
when the observable emits a value.
// ...\n\nconst myEpic = action$ =>\n action$.pipe(\n ofType("SOME_ACTION"), // Execute when the action type is 'INIT'\n switchMap(action =>\n subspace\n .trackEvent(MyContract, "MyEventName", { filter: {}, fromBlock: 1})\n .pipe(\n map(myAction) // Trigger redux action: MY_ACTION with the eventData\n )\n )\n );\n\n// ...\n
\n\n\n\nSee example DApp at https://github.com/embark-framework/subspace/tree/master/examples/react-example1
\n","site":{"data":{"sidebar":{"docs":[{"title":"How it works","path":"how-it-works.html"},{"title":"Getting Started","path":"getting-started.html","children":[{"title":"Installation","path":"#Installation"},{"title":"Importing the library","path":"#Importing-the-library"},{"title":"Connecting to a web3 provider","path":"#Connecting-to-a-web3-provider"},{"title":"Enhancing your contract objects","path":"#Enhancing-your-contract-objects"},{"title":"Reacting to data","path":"#Reacting-to-data"},{"title":"Tracking state","path":"#Tracking-state"},{"title":"Tracking events","path":"#Tracking-events"},{"title":"Tracking balances","path":"#Tracking-balances"},{"title":"Getting block data, gas prices and block time","path":"#Getting-block-data-gas-prices-and-block-time"},{"title":"Subscriptions","path":"#Subscriptions"},{"title":"Cleanup","path":"#Cleanup"}]},{"title":"Integrations","path":"empty","children":[{"title":"Overview","path":"integrations-overview.html"},{"title":"React","path":"react.html"},{"title":"Vue","path":"vue.html"},{"title":"Redux","path":"empty","children":[{"title":"redux","path":"redux.html"},{"title":"redux-observable","path":"redux-observable.html"}]},{"title":"reactive-graphql","path":"reactive-graphql.html"},{"title":"apollo-client","path":"apollo-client.html"}]},{"title":"Tutorial","path":"tutorial.html"},{"title":"API","path":"api.html","children":[{"title":"General","path":"#general"},{"title":"Contract methods","path":"#Contract-methods"},{"title":"Blocks, gas price and block time","path":"#Blocks-gas-price-and-block-time"},{"title":"Low level API for data tracking","path":"#Low-level-API-for-data-tracking"}]}]}}},"excerpt":"","more":"See example DApp at https://github.com/embark-framework/subspace/tree/master/examples/react-example1
\n"},{"_content":"# Vue\nVue provides the official npm package `vue-rx` that provides RxJS integration, which simplifies the use of Subspace with Vue.js\n\n### Example\n\nVue provides the official npm package vue-rx
that provides RxJS integration, which simplifies the use of Subspace with Vue.js
<template>\n <ul v-if="!!eventData$">\n <li><b>someValue: </b> {{eventData$.someValue}}</li>\n <li><b>anotherValue: </b> {{eventData$.anotherValue}}</li>\n </ul>\n</template>\n\n<script>\nexport default {\n name: 'MyComponent',\n props: {\n eventData: Object\n },\n subscriptions() { // provide Rx observables\n return {\n eventData$: this.eventData\n }\n }\n}\n</script>\n
\n\n\n\n<template>\n <div id="app">\n <button v-on:click="createTrx">Create a Transaction</button>\n <MyComponent v-bind:event-data="myEventObservable$" v-if="!!myEventObservable$" />\n </div>\n</template>\n\n<script>\nimport MyComponent from './components/MyComponent.vue';\nimport Subspace from "@embarklabs/subspace";\n\nexport default {\n name: 'app',\n data: function(){\n return {\n myEventObservable$: null,\n MyContractInstance: null\n };\n },\n created: async function(){\n this.MyContractInstance = ...; // TODO: obtain a web3.eth.contract instance\n\n const subspace = new Subspace(web3);\n await subspace.init();\n\n this.myEventObservable$ = subspace.trackEvent(this.MyContractInstance, "MyEvent", {filter: {}, fromBlock: 1 });\n }, \n methods: {\n createTrx: function(){\n this.MyContractInstance.methods\n .myFunction()\n .send({ from: web3.eth.defaultAccount });\n }\n },\n components: {\n MyComponent\n }\n}\n</script>\n
\n\n\n\nVue provides the official npm package vue-rx
that provides RxJS integration, which simplifies the use of Subspace with Vue.js
<template>\n <ul v-if="!!eventData$">\n <li><b>someValue: </b> {{eventData$.someValue}}</li>\n <li><b>anotherValue: </b> {{eventData$.anotherValue}}</li>\n </ul>\n</template>\n\n<script>\nexport default {\n name: 'MyComponent',\n props: {\n eventData: Object\n },\n subscriptions() { // provide Rx observables\n return {\n eventData$: this.eventData\n }\n }\n}\n</script>\n
\n\n\n\n<template>\n <div id="app">\n <button v-on:click="createTrx">Create a Transaction</button>\n <MyComponent v-bind:event-data="myEventObservable$" v-if="!!myEventObservable$" />\n </div>\n</template>\n\n<script>\nimport MyComponent from './components/MyComponent.vue';\nimport Subspace from "@embarklabs/subspace";\n\nexport default {\n name: 'app',\n data: function(){\n return {\n myEventObservable$: null,\n MyContractInstance: null\n };\n },\n created: async function(){\n this.MyContractInstance = ...; // TODO: obtain a web3.eth.contract instance\n\n const subspace = new Subspace(web3);\n await subspace.init();\n\n this.myEventObservable$ = subspace.trackEvent(this.MyContractInstance, "MyEvent", {filter: {}, fromBlock: 1 });\n }, \n methods: {\n createTrx: function(){\n this.MyContractInstance.methods\n .myFunction()\n .send({ from: web3.eth.defaultAccount });\n }\n },\n components: {\n MyComponent\n }\n}\n</script>\n
\n\n\n\nMyContractInstance
is a web3.eth.Contract
object pointing to a deployed contract address. You can use a DApp framework like Embark to easily import that contract instance: import { MyContract } from './embarkArtifacts/contracts';
, or use web3.js directly (just like in the example source code)\nexamples/react-redux
.\nMyContractInstance
is a web3.eth.Contract
object pointing to a deployed contract address. You can use a DApp framework like Embark to easily import that contract instance: import { MyContract } from './embarkArtifacts/contracts';
, or use web3.js directly (just like in the example source code)\nexamples/react-redux
.\nSubspace can be used with redux. Subspace returns Observables
, which you can subscribe to, and if this subscription has access to the redux store, it will be able to dispatch actions when the observable emits an event.
Here’s a simple example on how to setup Subspace to work with redux
:
import store from './store';\nimport web3 from './web3';\nimport Subspace from '@embarklabs/subspace';\nimport { myAction } from './actions';\n\nconst run = async () => {\n const MyContractInstance = ...; // TODO: obtain a web3.eth.contract instance\n\n const subspace = new Subspace(web3);\n await subspace.init();\n \n subspace.trackEvent(MyContractInstance, "MyEvent", {filter: {}, fromBlock: 1 })\n .subscribe(eventData => {\n store.dispatch(myAction(eventData));\n });\n}\n\nrun();\n
\n\nMyContractInstance
is a web3.eth.Contract
object pointing to a deployed contract address. You can use a DApp framework like Embark to easily import that contract instance: import { MyContract } from './embarkArtifacts/contracts';
, or use web3.js directly (just like in the example source code)\nimport { createStore } from 'redux';\nimport {myReducer} from './reducer';\n\nexport default store = createStore(myReducer);\n
\n\n\n\nimport { MY_ACTION } from "./constants";\n\nconst initialState = { \n data: {}\n};\n\nexport const myReducer = (state = initialState, action) => {\n switch (action.type) {\n case MY_ACTION:\n return { data: action.eventData };\n default:\n return state;\n }\n};\n
\n\n\n\nexport const MY_ACTION = 'MY_ACTION';\n
\n\n\n\nimport {MY_ACTION} from './constants.js';\n\nexport const myAction = eventData => ({type: MY_ACTION, eventData});\n
\n\n\n\nexamples/react-redux
.\nSubspace can be used with redux. Subspace returns Observables
, which you can subscribe to, and if this subscription has access to the redux store, it will be able to dispatch actions when the observable emits an event.
Here’s a simple example on how to setup Subspace to work with redux
:
import store from './store';\nimport web3 from './web3';\nimport Subspace from '@embarklabs/subspace';\nimport { myAction } from './actions';\n\nconst run = async () => {\n const MyContractInstance = ...; // TODO: obtain a web3.eth.contract instance\n\n const subspace = new Subspace(web3);\n await subspace.init();\n \n subspace.trackEvent(MyContractInstance, "MyEvent", {filter: {}, fromBlock: 1 })\n .subscribe(eventData => {\n store.dispatch(myAction(eventData));\n });\n}\n\nrun();\n
\n\nMyContractInstance
is a web3.eth.Contract
object pointing to a deployed contract address. You can use a DApp framework like Embark to easily import that contract instance: import { MyContract } from './embarkArtifacts/contracts';
, or use web3.js directly (just like in the example source code)\nimport { createStore } from 'redux';\nimport {myReducer} from './reducer';\n\nexport default store = createStore(myReducer);\n
\n\n\n\nimport { MY_ACTION } from "./constants";\n\nconst initialState = { \n data: {}\n};\n\nexport const myReducer = (state = initialState, action) => {\n switch (action.type) {\n case MY_ACTION:\n return { data: action.eventData };\n default:\n return state;\n }\n};\n
\n\n\n\nexport const MY_ACTION = 'MY_ACTION';\n
\n\n\n\nimport {MY_ACTION} from './constants.js';\n\nexport const myAction = eventData => ({type: MY_ACTION, eventData});\n
\n\n\n\nexamples/react-redux
.\nnew Subspace(web3 [, options])
Constructor.
\nParameters
\nweb3
- Object
: a web3.js
object.options
- Object
(optional): Options used to initialize SubspacedbFilename
- String
(optional): Name of the database where the information will be stored (default 'subspace.db'
)callInterval
- Interval of time in milliseconds to query a contract/address to determine changes in state or balance. It’s only used with HttpProviders (default: undefined
. Obtains data every block using the average block time as an interval).refreshLastNBlocks
- Ignores last N blocks (from current block), stored in the local db and refresh them via a web3 subscription. Useful for possible reorgs (default: 12),disableSubscriptions
- Subspace by default will attempt to use websocket subscriptions if the current provider supports them, otherwise it will use polling because it asumes the provider is an HttpProvider. This functionality can be disabled by passing true to this option. (default: undefined)init()
Initializes Subspace
\nReturnsPromise
that once it’s resolved, will mean that Subspace is available to use
close()
Dispose and perform the cleanup necessary to remove the internal subscriptions and interval timers created by Subspace during its normal execution.
\ncontract(instance|{abi,address})
Adds a track
method to the web3 contract objects. You can obtain this functionality by passing a web3.eth.Contract
instance, or the abi
and address
of your contract
Returnsweb3.eth.Contract
object enhanced with .track()
functions for methods and events.
myContract.events.MyEvent.track([options])
Track a contract event.
\nParameters
\noptions
- Object
(optional): web3 filter options object to limit the number of events based on a block number range, or indexed filtersfilter
- Object
(optional): Lets you filter events by indexed parameters, e.g. {filter: {myNumber: [12,13]}}
means all events where "myNumber"
is 12
or 13
.fromBlock
- Number
(optional): The block number from which to get events on.toBlock
- Number
(optional): The block number to get events up to (Defaults to "latest"
)topics
- Array
(optional): This allows you to manually set the topics for the event filter. If given the filter property and event signature, (topic[0]
) will not be set automatically.ReturnsRxJS Observable
which will stream the event returnValues
.
myContract.methods.myMethod([param1[, ...]]).track([callOptions])
Track a constant function / contract state variable on each block mined, or depending on the callInterval
option used during Subspace initialization.
Parameters
\ncallOptions
- Object
(optional): The options used for calling.from
- String
(optional): The address the call “transaction” should be made from.gasPrice
- String
(optional): The gas price in wei to use for this call “transaction”.gas
- Number
(optional): The maximum gas provided for this call “transaction” (gas limit).ReturnsRxJS Observable
which will stream the function / variable values. Data type will depend on the contract function invoked.
myContract.trackBalance(address [, tokenAddress])
Track a contract’s balance changes for an address on each block mined, or depending on the callInterval
option used during Subspace initialization.
Parameters
\naddress
- String
: The address to get the balance of.tokenAddress
- String
(optional): If you want to track the balance for an ERC20 contract, here you can specify the token address. Otherwise, Only ETH balances will be returned.ReturnsRxJS Observable
which will stream a string containing the address balance.
trackBlock()
Receive the block information for any new block. It’s the reactive equivalent to web3.eth.getBlock("latest")
.
ReturnsRxJS Observable
which will stream a block object for the latest block received
trackBlockNumber()
Returns the latest block number. It’s the reactive equivalent to web3.eth.getBlockNumber
.
ReturnsRxJS Observable
with the latest block number
trackGasPrice()
Returns the current gas price oracle. It’s the reactive equivalent to web3.eth.getGasPrice
.
ReturnsRxJS Observable
with the average gas price in wei.
trackAverageBlocktime()
Average block time of the last 10 blocks.
\nReturnsRxJS Observable
with the moving average block time of the last 10 blocks. The time is returned in milliseconds:
These are used in case you don’t want to decorate your web3 contract objects, or if you want to track the balance for an specific address.
\ntrackEvent(contractObject, eventName [, options])
Track a contract event.
\nParameters
\ncontractObject
- web3.eth.Contract
: An already initialized contract object pointing to an address and containing a valid ABI.eventName
- String
: The name of the event to subscribe.options
- Object
(optional): web3 filter options object to limit the number of events based on a block number range, or indexed filtersfilter
- Object
(optional): Lets you filter events by indexed parameters, e.g. {filter: {myNumber: [12,13]}}
means all events where "myNumber"
is 12
or 13
.fromBlock
- Number
(optional): The block number from which to get events on.toBlock
- Number
(optional): The block number to get events up to (Defaults to "latest"
)topics
- Array
(optional): This allows you to manually set the topics for the event filter. If given the filter property and event signature, (topic[0]
) will not be set automatically.ReturnsRxJS Observable
which will stream the event returnValues
.
trackProperty(contractObject, functionName [, functionArgs] [, callOptions])
Track a constant function / contract state variable on each block mined, or depending on the callInterval
option used during Subspace initialization.
Parameters
\ncontractObject
- web3.eth.Contract
: An already initialized contract object pointing to an address and containing a valid ABI.functionName
- String
: Name of the function or variable whose values will be tracked.functionArgs
- Array
(optional): Array of arguments that the tracked function receivescallOptions
- Object
(optional): The options used for calling.from
- String
(optional): The address the call “transaction” should be made from.gasPrice
- String
(optional): The gas price in wei to use for this call “transaction”.gas
- Number
(optional): The maximum gas provided for this call “transaction” (gas limit).ReturnsRxJS Observable
which will stream the function / variable values. Data type will depend on the contract function invoked.
trackBalance(address [, tokenAddress])
Track balance changes for an address on each block mined, or depending on the callInterval
option used during Subspace initialization.
Parameters
\naddress
- String
: The address to get the balance of.tokenAddress
- String
(optional): If you want to track the balance for an ERC20 contract, here you can specify the token address. Otherwise, Only ETH balances will be returned.ReturnsRxJS Observable
which will stream a string containing the address balance.
trackLogs(options [, abi])
Tracks incoming logs, filtered by the given options.
\nParameters
\noptions
- Object
(optional): web3 filter options object to limit the number of logsaddress
- String|Array
(optional): An address or a list of addresses to only get logs from particular account(s).fromBlock
- Number
(optional): The block number from which to get events on.topics
- Array
(optional): An array of values which must each appear in the log entries. The order is important, if you want to leave topics out use null, e.g. [null, ‘0x00…’]. You can also pass another array for each topic with options for that topic e.g. [null, [‘option1’, ‘option2’]].abi
- Array
(optional): Array containing the ABI for the inputs of the logs received. It will automatically decode the logs using this ABI instead of returning the hexadecimal data.ReturnsRxJS Observable
which will stream the logs. If the inputs ABI is included in the call, the logs will be automatically decoded.
new Subspace(web3 [, options])
Constructor.
\nParameters
\nweb3
- Object
: a web3.js
object.options
- Object
(optional): Options used to initialize SubspacedbFilename
- String
(optional): Name of the database where the information will be stored (default 'subspace.db'
)callInterval
- Interval of time in milliseconds to query a contract/address to determine changes in state or balance. It’s only used with HttpProviders (default: undefined
. Obtains data every block using the average block time as an interval).refreshLastNBlocks
- Ignores last N blocks (from current block), stored in the local db and refresh them via a web3 subscription. Useful for possible reorgs (default: 12),disableSubscriptions
- Subspace by default will attempt to use websocket subscriptions if the current provider supports them, otherwise it will use polling because it asumes the provider is an HttpProvider. This functionality can be disabled by passing true to this option. (default: undefined)init()
Initializes Subspace
\nReturnsPromise
that once it’s resolved, will mean that Subspace is available to use
close()
Dispose and perform the cleanup necessary to remove the internal subscriptions and interval timers created by Subspace during its normal execution.
\ncontract(instance|{abi,address})
Adds a track
method to the web3 contract objects. You can obtain this functionality by passing a web3.eth.Contract
instance, or the abi
and address
of your contract
Returnsweb3.eth.Contract
object enhanced with .track()
functions for methods and events.
myContract.events.MyEvent.track([options])
Track a contract event.
\nParameters
\noptions
- Object
(optional): web3 filter options object to limit the number of events based on a block number range, or indexed filtersfilter
- Object
(optional): Lets you filter events by indexed parameters, e.g. {filter: {myNumber: [12,13]}}
means all events where "myNumber"
is 12
or 13
.fromBlock
- Number
(optional): The block number from which to get events on.toBlock
- Number
(optional): The block number to get events up to (Defaults to "latest"
)topics
- Array
(optional): This allows you to manually set the topics for the event filter. If given the filter property and event signature, (topic[0]
) will not be set automatically.ReturnsRxJS Observable
which will stream the event returnValues
.
myContract.methods.myMethod([param1[, ...]]).track([callOptions])
Track a constant function / contract state variable on each block mined, or depending on the callInterval
option used during Subspace initialization.
Parameters
\ncallOptions
- Object
(optional): The options used for calling.from
- String
(optional): The address the call “transaction” should be made from.gasPrice
- String
(optional): The gas price in wei to use for this call “transaction”.gas
- Number
(optional): The maximum gas provided for this call “transaction” (gas limit).ReturnsRxJS Observable
which will stream the function / variable values. Data type will depend on the contract function invoked.
myContract.trackBalance(address [, tokenAddress])
Track a contract’s balance changes for an address on each block mined, or depending on the callInterval
option used during Subspace initialization.
Parameters
\naddress
- String
: The address to get the balance of.tokenAddress
- String
(optional): If you want to track the balance for an ERC20 contract, here you can specify the token address. Otherwise, Only ETH balances will be returned.ReturnsRxJS Observable
which will stream a string containing the address balance.
trackBlock()
Receive the block information for any new block. It’s the reactive equivalent to web3.eth.getBlock("latest")
.
ReturnsRxJS Observable
which will stream a block object for the latest block received
trackBlockNumber()
Returns the latest block number. It’s the reactive equivalent to web3.eth.getBlockNumber
.
ReturnsRxJS Observable
with the latest block number
trackGasPrice()
Returns the current gas price oracle. It’s the reactive equivalent to web3.eth.getGasPrice
.
ReturnsRxJS Observable
with the average gas price in wei.
trackAverageBlocktime()
Average block time of the last 10 blocks.
\nReturnsRxJS Observable
with the moving average block time of the last 10 blocks. The time is returned in milliseconds:
These are used in case you don’t want to decorate your web3 contract objects, or if you want to track the balance for an specific address.
\ntrackEvent(contractObject, eventName [, options])
Track a contract event.
\nParameters
\ncontractObject
- web3.eth.Contract
: An already initialized contract object pointing to an address and containing a valid ABI.eventName
- String
: The name of the event to subscribe.options
- Object
(optional): web3 filter options object to limit the number of events based on a block number range, or indexed filtersfilter
- Object
(optional): Lets you filter events by indexed parameters, e.g. {filter: {myNumber: [12,13]}}
means all events where "myNumber"
is 12
or 13
.fromBlock
- Number
(optional): The block number from which to get events on.toBlock
- Number
(optional): The block number to get events up to (Defaults to "latest"
)topics
- Array
(optional): This allows you to manually set the topics for the event filter. If given the filter property and event signature, (topic[0]
) will not be set automatically.ReturnsRxJS Observable
which will stream the event returnValues
.
trackProperty(contractObject, functionName [, functionArgs] [, callOptions])
Track a constant function / contract state variable on each block mined, or depending on the callInterval
option used during Subspace initialization.
Parameters
\ncontractObject
- web3.eth.Contract
: An already initialized contract object pointing to an address and containing a valid ABI.functionName
- String
: Name of the function or variable whose values will be tracked.functionArgs
- Array
(optional): Array of arguments that the tracked function receivescallOptions
- Object
(optional): The options used for calling.from
- String
(optional): The address the call “transaction” should be made from.gasPrice
- String
(optional): The gas price in wei to use for this call “transaction”.gas
- Number
(optional): The maximum gas provided for this call “transaction” (gas limit).ReturnsRxJS Observable
which will stream the function / variable values. Data type will depend on the contract function invoked.
trackBalance(address [, tokenAddress])
Track balance changes for an address on each block mined, or depending on the callInterval
option used during Subspace initialization.
Parameters
\naddress
- String
: The address to get the balance of.tokenAddress
- String
(optional): If you want to track the balance for an ERC20 contract, here you can specify the token address. Otherwise, Only ETH balances will be returned.ReturnsRxJS Observable
which will stream a string containing the address balance.
trackLogs(options [, abi])
Tracks incoming logs, filtered by the given options.
\nParameters
\noptions
- Object
(optional): web3 filter options object to limit the number of logsaddress
- String|Array
(optional): An address or a list of addresses to only get logs from particular account(s).fromBlock
- Number
(optional): The block number from which to get events on.topics
- Array
(optional): An array of values which must each appear in the log entries. The order is important, if you want to leave topics out use null, e.g. [null, ‘0x00…’]. You can also pass another array for each topic with options for that topic e.g. [null, [‘option1’, ‘option2’]].abi
- Array
(optional): Array containing the ABI for the inputs of the logs received. It will automatically decode the logs using this ABI instead of returning the hexadecimal data.ReturnsRxJS Observable
which will stream the logs. If the inputs ABI is included in the call, the logs will be automatically decoded.
Observable
type can be used to model push-based data sources such as DOM events, timer intervals, and sockets. In addition, observables are:\n- Compositional: Observables can be composed with higher-order combinators.\n- Lazy: Observables do not start emitting data until an observer has subscribed.\nview
function when they're defined as public
. The functionName
would be the same as the variable name, and functionArgs
will have a value when the type is a mapping
or array (since these require an index value to query them).\nclose()
will dispose any web3 subscription created when using a Subspace tracking method, however any subscription to an observable must still be unsubscribed manually. The npm package subsink
can be used to clear all the observables' subscriptions at once.\nObservable
type can be used to model push-based data sources such as DOM events, timer intervals, and sockets. In addition, observables are:\n- Compositional: Observables can be composed with higher-order combinators.\n- Lazy: Observables do not start emitting data until an observer has subscribed.\nview
function when they're defined as public
. The functionName
would be the same as the variable name, and functionArgs
will have a value when the type is a mapping
or array (since these require an index value to query them).\nclose()
will dispose any web3 subscription created when using a Subspace tracking method, however any subscription to an observable must still be unsubscribed manually. The npm package subsink
can be used to clear all the observables' subscriptions at once.\nSubspace can be used in browser, node and native script environments. To get started install the package @embarklabs/subspace
using npm
or yarn
by executing this command in your project directory:
# Using npm\nnpm install --save @embarklabs/subspace\n\n# Using yarn\nyarn add @embarklabs/subspace \n
\n\n\n\n// ESM (might require babel / browserify)\nimport Subspace from '@embarklabs/subspace'; \n\n// CommonJS\nconst Subspace = require('@embarklabs/subspace'); \n
\n\n\n\nTo interact with the EVM, Subspace requires a valid Web3 object, connected to a provider
\n\n\nconst subspace = new Subspace(web3);\nawait subspace.init();\n
\n\n\n\nIn addition to the provider, Subspace
also accepts an options
object with settings that can change its behavior:
dbFilename
- Name of the database where the information will be stored (default 'subspace.db'
)callInterval
- Interval of time in milliseconds to query a contract/address to determine changes in state or balance. It’s only used with HttpProviders (default: undefined
. Obtains data every block using the average block time as an interval).refreshLastNBlocks
- Ignores last N blocks (from current block), stored in the local db and refresh them via a web3 subscription. Useful for possible reorgs (default: 12),disableSubscriptions
- Subspace by default will attempt to use websocket subscriptions if the current provider supports them, otherwise it will use polling because it asumes the provider is an HttpProvider. This functionality can be disabled by passing true to this option. (default: undefined
)Subspace provides a method to enhance your web3 Contract objects: subspace.contract(instance|{abi,address})
. Calling this method will return a new contract object decorated with a .track()
method for your contract view functions and events.
const myRxContract = subspace.contract(myContractInstance);\n
\n\n\n\nYou can also instantiate a contract directly by passing the contract ABI and its address:
\n\n\nconst myRXContract = subspace.contract({abi: ...., address: '0x1234...CDEF'})\n
\n\n\n\nOnce it’s initialized, you can use Subspace‘s methods to track the contract state, events and balances. These functions return RxJS Observables which you can subscribe to, and obtain and transform the observed data via operators.
\nObservable
type can be used to model push-based data sources such as DOM events, timer intervals, and sockets. In addition, observables are:\n- Compositional: Observables can be composed with higher-order combinators.\n- Lazy: Observables do not start emitting data until an observer has subscribed.\nYou can track changes to a contract state variable, by specifying the view function and arguments to call and query the contract.
\n\n\nconst stateObservable$ = Contract.methods.functionName(functionArgs).track();\n
\n\n\n\nview
function when they're defined as public
. The functionName
would be the same as the variable name, and functionArgs
will have a value when the type is a mapping
or array (since these require an index value to query them).\nExample:
\n\n\nconst productTitle$ = ProductList.methods.products(0).track().map("title");\nproductTitle$.subscribe((title) => console.log("product title is " + title));\n\n\n// Alternative using Subspace low level API\nconst producTitle$ = subspace.trackProperty(ProductList, "products", [0], {from: web3.eth.defaultAccount});\n...\n
\n\n\n\nThe subscription will be triggered whenever the title changes
\nYou can track events and react to their returned values.
\n\n\nconst eventObservable$ = Contract.event.eventName.track();\n
\n\n\n\nExample:
\n\n\nconst rating$ = Product.events.Rating.track().map("rating")).pipe(map(x => parseInt(x)));\nrating$.subscribe((rating) => console.log("rating received: " + rating));\n\n\n// Alternative using Subspace low level API\nconst rating$ = subspace.trackEvent(Product, "Rating", {fromBlock: 0});\n...\n
\n\n\n\nEvent Sourcing
\nYou can easily do event sourcing with subspace.
\nFor e.g: if you needed to get the average rating of the last 5 events:
\n\n\nimport { $average, $latest } from "@embarklabs/subspace";\n\nconst rating$ = Product.events.Rating.track().map("rating")).pipe(map(x => parseInt(x)));\n\nrating$.pipe($latest(5), $average()).subscribe((rating) => {\n console.log("average rating of the last 5 events is " + rating)\n});\n
\n\n\n\nYou can also track changes in both ETH and ERC20 token balances for each mined block or time interval depending on the callInterval
configured.
Tracking ETH balance in an address:
\n\n\nconst address = "0x0001020304050607080900010203040506070809";\n\nsubspace.trackBalance(address).subscribe((balance) => {\n console.log("ETH balance is ", balance)\n});\n
\n\n\n\nTracking ETH balance in a Contract:
\n\n\nContract.trackBalance().subscribe((balance) => {\n console.log("ETH balance is ", balance)\n});\n
\n\n\n\nTracking an ERC20 balance in a Contract:
\n\n\nconst tokenAddress = "0x744d70fdbe2ba4cf95131626614a1763df805b9e"; // SNT Address\n\nconst myBalanceObservable$ = Contract.trackBalance(tokenAddress);\n
\n\n\n\nSubspace also provides a way to always receive the latest block object:
\n\n\nsubspace.trackBlock().subscribe(block => {\n console.log("The latest block data: ", block);\n});\n
\n\n\n\nIf you don’t need all the block information, but just the block number, you can use instead:
\n\n\nsubspace.trackBlockNumber().subscribe(blockNumber => {\n console.log("The latest block number: ", blockNumber);\n});\n
\n\n\n\nYou can also access the average block time. This takes in account only the last 10 blocks:
\n\n\nsubspace.trackAverageBlocktime().subscribe(blocktimeMS => {\n console.log("The average block time in milliseconds is: ", blocktimeMS);\n});\n
\n\n\n\nFinally, if you want to obtain the most up to date median gas price:
\n\n\nsubspace.trackGasPrice().subscribe(gasPrice => {\n console.log("Gas price in wei", gasPrice);\n});\n
\n\n\n\nOnce you have an Observable
, you may receive a stream of data by creating a subscription. Subscriptions are triggered each time an observable emits a new value. These subscription receive a callback that must have a parameter which represents the value received from the observable (a contract state variable, an event, or the balance of an address); and they return an object representing the subscription.
Subscriptions can be disposed by executing the method unsubscribe()
liberating the resource held by it:
const myBalanceObservable$ = subspace.trackBalance(address, tokenAddress);\nconst subscription = myBalanceObservable$.subscribe(value => { \n console.log("The balance is: ", value); \n});\n\n// ...\n\nsubscription.unsubscribe();\n
\n\n\n\nIf Subspace is not needed anymore, you need can invoke close()
to dispose and perform the cleanup necessary to remove the internal subscriptions and interval timers created by Subspace during its normal execution, thus avoiding any potential memory leak.
subspace.close();\n
\n\nclose()
will dispose any web3 subscription created when using a Subspace tracking method, however any subscription to an observable must still be unsubscribed manually. The npm package subsink
can be used to clear all the observables' subscriptions at once.\nSubspace can be used in browser, node and native script environments. To get started install the package @embarklabs/subspace
using npm
or yarn
by executing this command in your project directory:
# Using npm\nnpm install --save @embarklabs/subspace\n\n# Using yarn\nyarn add @embarklabs/subspace \n
\n\n\n\n// ESM (might require babel / browserify)\nimport Subspace from '@embarklabs/subspace'; \n\n// CommonJS\nconst Subspace = require('@embarklabs/subspace'); \n
\n\n\n\nTo interact with the EVM, Subspace requires a valid Web3 object, connected to a provider
\n\n\nconst subspace = new Subspace(web3);\nawait subspace.init();\n
\n\n\n\nIn addition to the provider, Subspace
also accepts an options
object with settings that can change its behavior:
dbFilename
- Name of the database where the information will be stored (default 'subspace.db'
)callInterval
- Interval of time in milliseconds to query a contract/address to determine changes in state or balance. It’s only used with HttpProviders (default: undefined
. Obtains data every block using the average block time as an interval).refreshLastNBlocks
- Ignores last N blocks (from current block), stored in the local db and refresh them via a web3 subscription. Useful for possible reorgs (default: 12),disableSubscriptions
- Subspace by default will attempt to use websocket subscriptions if the current provider supports them, otherwise it will use polling because it asumes the provider is an HttpProvider. This functionality can be disabled by passing true to this option. (default: undefined
)Subspace provides a method to enhance your web3 Contract objects: subspace.contract(instance|{abi,address})
. Calling this method will return a new contract object decorated with a .track()
method for your contract view functions and events.
const myRxContract = subspace.contract(myContractInstance);\n
\n\n\n\nYou can also instantiate a contract directly by passing the contract ABI and its address:
\n\n\nconst myRXContract = subspace.contract({abi: ...., address: '0x1234...CDEF'})\n
\n\n\n\nOnce it’s initialized, you can use Subspace‘s methods to track the contract state, events and balances. These functions return RxJS Observables which you can subscribe to, and obtain and transform the observed data via operators.
\nObservable
type can be used to model push-based data sources such as DOM events, timer intervals, and sockets. In addition, observables are:\n- Compositional: Observables can be composed with higher-order combinators.\n- Lazy: Observables do not start emitting data until an observer has subscribed.\nYou can track changes to a contract state variable, by specifying the view function and arguments to call and query the contract.
\n\n\nconst stateObservable$ = Contract.methods.functionName(functionArgs).track();\n
\n\n\n\nview
function when they're defined as public
. The functionName
would be the same as the variable name, and functionArgs
will have a value when the type is a mapping
or array (since these require an index value to query them).\nExample:
\n\n\nconst productTitle$ = ProductList.methods.products(0).track().map("title");\nproductTitle$.subscribe((title) => console.log("product title is " + title));\n\n\n// Alternative using Subspace low level API\nconst producTitle$ = subspace.trackProperty(ProductList, "products", [0], {from: web3.eth.defaultAccount});\n...\n
\n\n\n\nThe subscription will be triggered whenever the title changes
\nYou can track events and react to their returned values.
\n\n\nconst eventObservable$ = Contract.event.eventName.track();\n
\n\n\n\nExample:
\n\n\nconst rating$ = Product.events.Rating.track().map("rating")).pipe(map(x => parseInt(x)));\nrating$.subscribe((rating) => console.log("rating received: " + rating));\n\n\n// Alternative using Subspace low level API\nconst rating$ = subspace.trackEvent(Product, "Rating", {fromBlock: 0});\n...\n
\n\n\n\nEvent Sourcing
\nYou can easily do event sourcing with subspace.
\nFor e.g: if you needed to get the average rating of the last 5 events:
\n\n\nimport { $average, $latest } from "@embarklabs/subspace";\n\nconst rating$ = Product.events.Rating.track().map("rating")).pipe(map(x => parseInt(x)));\n\nrating$.pipe($latest(5), $average()).subscribe((rating) => {\n console.log("average rating of the last 5 events is " + rating)\n});\n
\n\n\n\nYou can also track changes in both ETH and ERC20 token balances for each mined block or time interval depending on the callInterval
configured.
Tracking ETH balance in an address:
\n\n\nconst address = "0x0001020304050607080900010203040506070809";\n\nsubspace.trackBalance(address).subscribe((balance) => {\n console.log("ETH balance is ", balance)\n});\n
\n\n\n\nTracking ETH balance in a Contract:
\n\n\nContract.trackBalance().subscribe((balance) => {\n console.log("ETH balance is ", balance)\n});\n
\n\n\n\nTracking an ERC20 balance in a Contract:
\n\n\nconst tokenAddress = "0x744d70fdbe2ba4cf95131626614a1763df805b9e"; // SNT Address\n\nconst myBalanceObservable$ = Contract.trackBalance(tokenAddress);\n
\n\n\n\nSubspace also provides a way to always receive the latest block object:
\n\n\nsubspace.trackBlock().subscribe(block => {\n console.log("The latest block data: ", block);\n});\n
\n\n\n\nIf you don’t need all the block information, but just the block number, you can use instead:
\n\n\nsubspace.trackBlockNumber().subscribe(blockNumber => {\n console.log("The latest block number: ", blockNumber);\n});\n
\n\n\n\nYou can also access the average block time. This takes in account only the last 10 blocks:
\n\n\nsubspace.trackAverageBlocktime().subscribe(blocktimeMS => {\n console.log("The average block time in milliseconds is: ", blocktimeMS);\n});\n
\n\n\n\nFinally, if you want to obtain the most up to date median gas price:
\n\n\nsubspace.trackGasPrice().subscribe(gasPrice => {\n console.log("Gas price in wei", gasPrice);\n});\n
\n\n\n\nOnce you have an Observable
, you may receive a stream of data by creating a subscription. Subscriptions are triggered each time an observable emits a new value. These subscription receive a callback that must have a parameter which represents the value received from the observable (a contract state variable, an event, or the balance of an address); and they return an object representing the subscription.
Subscriptions can be disposed by executing the method unsubscribe()
liberating the resource held by it:
const myBalanceObservable$ = subspace.trackBalance(address, tokenAddress);\nconst subscription = myBalanceObservable$.subscribe(value => { \n console.log("The balance is: ", value); \n});\n\n// ...\n\nsubscription.unsubscribe();\n
\n\n\n\nIf Subspace is not needed anymore, you need can invoke close()
to dispose and perform the cleanup necessary to remove the internal subscriptions and interval timers created by Subspace during its normal execution, thus avoiding any potential memory leak.
subspace.close();\n
\n\nclose()
will dispose any web3 subscription created when using a Subspace tracking method, however any subscription to an observable must still be unsubscribed manually. The npm package subsink
can be used to clear all the observables' subscriptions at once.\nWelcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.
\n$ hexo new "My New Post"\n
\n\n\n\nMore info: Writing
\n$ hexo server\n
\n\n\n\nMore info: Server
\n$ hexo generate\n
\n\n\n\nMore info: Generating
\n$ hexo deploy\n
\n\n\n\nMore info: Deployment
\n","site":{"data":{"sidebar":{"docs":[{"title":"How it works","path":"how-it-works.html"},{"title":"Getting Started","path":"getting-started.html","children":[{"title":"Installation","path":"#Installation"},{"title":"Importing the library","path":"#Importing-the-library"},{"title":"Connecting to a web3 provider","path":"#Connecting-to-a-web3-provider"},{"title":"Enhancing your contract objects","path":"#Enhancing-your-contract-objects"},{"title":"Reacting to data","path":"#Reacting-to-data"},{"title":"Tracking state","path":"#Tracking-state"},{"title":"Tracking events","path":"#Tracking-events"},{"title":"Tracking balances","path":"#Tracking-balances"},{"title":"Getting block data, gas prices and block time","path":"#Getting-block-data-gas-prices-and-block-time"},{"title":"Subscriptions","path":"#Subscriptions"},{"title":"Cleanup","path":"#Cleanup"}]},{"title":"Integrations","path":"empty","children":[{"title":"Overview","path":"integrations-overview.html"},{"title":"React","path":"react.html"},{"title":"Vue","path":"vue.html"},{"title":"Redux","path":"empty","children":[{"title":"redux","path":"redux.html"},{"title":"redux-observable","path":"redux-observable.html"}]},{"title":"reactive-graphql","path":"reactive-graphql.html"},{"title":"apollo-client","path":"apollo-client.html"}]},{"title":"Tutorial","path":"tutorial.html"},{"title":"API","path":"api.html","children":[{"title":"General","path":"#general"},{"title":"Contract methods","path":"#Contract-methods"},{"title":"Blocks, gas price and block time","path":"#Blocks-gas-price-and-block-time"},{"title":"Low level API for data tracking","path":"#Low-level-API-for-data-tracking"}]}]}}},"excerpt":"","more":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.
\n$ hexo new "My New Post"\n
\n\n\n\nMore info: Writing
\n$ hexo server\n
\n\n\n\nMore info: Server
\n$ hexo generate\n
\n\n\n\nMore info: Generating
\n$ hexo deploy\n
\n\n\n\nMore info: Deployment
\n"}],"PostAsset":[],"PostCategory":[],"PostTag":[],"Tag":[]}}