<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[inpublic.space]]></title><description><![CDATA[programming, electronics hardware, and all between]]></description><link>https://inpublic.space/</link><image><url>https://inpublic.space/favicon.png</url><title>inpublic.space</title><link>https://inpublic.space/</link></image><generator>Ghost 3.41</generator><lastBuildDate>Thu, 23 Apr 2026 03:25:56 GMT</lastBuildDate><atom:link href="https://inpublic.space/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[flip-dots in 2023]]></title><description><![CDATA[<figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Integrated a custom controller into a sign from &#39;99. This old flip dot sign has a nice tactile feel to it but is modern and sleek enough to work in my space. It almost can connect to the internet and show some fun things – my journey to this working</p></blockquote></figure>]]></description><link>https://inpublic.space/flip-dots-in-2022/</link><guid isPermaLink="false">643902e5b8a3f805e5296f75</guid><dc:creator><![CDATA[inpublic]]></dc:creator><pubDate>Fri, 14 Apr 2023 07:44:13 GMT</pubDate><media:content url="https://inpublic.space/content/images/2023/04/tweet-7-1.jpeg" medium="image"/><content:encoded><![CDATA[<figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><img src="https://inpublic.space/content/images/2023/04/tweet-7-1.jpeg" alt="flip-dots in 2023"><p lang="en" dir="ltr">Integrated a custom controller into a sign from &#39;99. This old flip dot sign has a nice tactile feel to it but is modern and sleek enough to work in my space. It almost can connect to the internet and show some fun things – my journey to this working sign 🧵 <a href="https://t.co/KLjbLPORcf">pic.twitter.com/KLjbLPORcf</a></p>&mdash; iain (@isiain) <a href="https://twitter.com/isiain/status/1622596035813191680?ref_src=twsrc%5Etfw">February 6, 2023</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><p>I integrated a custom controller into a sign from '99. This old flip dot sign has a nice tactile feel to it but is modern and sleek enough to work in my space. It almost can connect to the internet and show some fun things – my journey to this working sign.</p><p>There are a few ways to acquire a flip-dot sign: </p><p>	1. Purchase an old bus sign</p><p>	2. Purchase a new flip-dot sign</p><p>	3. Find a old flip-dot modules or panels and build own driver controllers.</p><p>I chose #2 for the vintage aspect, relatively reasonable cost, and included driver boards. Public transit enthusiasts are fantastic :). Unfortunately these displays are hard to maintain, repair, and even find these days since they are being more often destroyed or in a broken state.</p><figure class="kg-card kg-image-card"><img src="https://inpublic.space/content/images/2023/04/tweet-3.jpeg" class="kg-image" alt="flip-dots in 2023" srcset="https://inpublic.space/content/images/size/w600/2023/04/tweet-3.jpeg 600w, https://inpublic.space/content/images/size/w1000/2023/04/tweet-3.jpeg 1000w, https://inpublic.space/content/images/2023/04/tweet-3.jpeg 1200w" sizes="(min-width: 720px) 720px"></figure><p>Thankfully, this sign I bought used an RS-485 based protocol which I'm familiar with from <a href="https://t.co/JGc6kDlDje" rel="noopener noreferrer nofollow">https://shop.inpublic.space/products/esp32-c3-dmx-wifi-ble-bridge…</a>. The sign needs 2 12v control signals for power and LED on. Using the expansion ports of this board I added a voltage converter and a resistor divider from 24v-&gt;12v</p><figure class="kg-card kg-image-card"><img src="https://inpublic.space/content/images/2023/04/tweet-2.jpeg" class="kg-image" alt="flip-dots in 2023" srcset="https://inpublic.space/content/images/size/w600/2023/04/tweet-2.jpeg 600w, https://inpublic.space/content/images/2023/04/tweet-2.jpeg 893w" sizes="(min-width: 720px) 720px"></figure><p>I tried to use the official connector but different pin types and shipping times I purchased a similar looking socket and hoped for the best. Ended up just soldering directly to the interface board. I could fit the new control hardware in the empty space in the sign enclosure.</p><p>After getting the pinout double checked and setup I tried to power on the sign. Nothing. Again, nothing. My control board drew its expected 30ma of current but nothing from the sign.</p><figure class="kg-card kg-image-card"><img src="https://inpublic.space/content/images/2023/04/tweet-4.jpeg" class="kg-image" alt="flip-dots in 2023" srcset="https://inpublic.space/content/images/size/w600/2023/04/tweet-4.jpeg 600w, https://inpublic.space/content/images/size/w1000/2023/04/tweet-4.jpeg 1000w, https://inpublic.space/content/images/2023/04/tweet-4.jpeg 1140w" sizes="(min-width: 720px) 720px"></figure><p>Turns out I just had to push in the sign’s interface board connector a little farther <em>phew</em>.</p><p>The sign now powers up! But all I see are LEDs and an expected 1amp power draw. After a few hours of re-configuring all of the serial pins, software settings, port direction settings, and checking the signal on a scope, I decided to double-check the connector.</p><p>Turns out, I swapped the RS485 inverting and non-inverting input, that explains why it didn’t work. After flipping the connectors to the correct polarity, the sign was happily switching!</p><figure class="kg-card kg-image-card"><img src="https://inpublic.space/content/images/2023/04/tweet-6.jpeg" class="kg-image" alt="flip-dots in 2023" srcset="https://inpublic.space/content/images/size/w600/2023/04/tweet-6.jpeg 600w, https://inpublic.space/content/images/size/w1000/2023/04/tweet-6.jpeg 1000w, https://inpublic.space/content/images/2023/04/tweet-6.jpeg 1200w" sizes="(min-width: 720px) 720px"></figure><p>Okay, maybe not so happily. Some flip dots don’t, well, flip. The dots that weren’t flipping included the last message but some were random. Sadly it wasn’t a pretty broken but a “this is broken” look… I tried increasing the line delay to no avail – the random inversions stayed.</p><p>The culprit was simple – my power supply was cutting off. These signs need a lot of current to do the flippy thing. Without real docs, I saw estimates from 2 - 6 amps. My sign was small so I guessed 2 amps would  be fine. Wrong – 3.5 amps fixed it. Now every flipper flips</p><figure class="kg-card kg-image-card"><img src="https://inpublic.space/content/images/2023/04/tweet-7.jpeg" class="kg-image" alt="flip-dots in 2023" srcset="https://inpublic.space/content/images/size/w600/2023/04/tweet-7.jpeg 600w, https://inpublic.space/content/images/size/w1000/2023/04/tweet-7.jpeg 1000w, https://inpublic.space/content/images/2023/04/tweet-7.jpeg 1200w" sizes="(min-width: 720px) 720px"></figure><p>Now that I have a working hardware setup, I want to enclose everything in the sign, write some fancy firmware and run a single power wire down the wall form the sign. Also need a new name-brand power supply (4-5a 24v power brick style). Need to think about what this will show.</p><p>Thanks to Joe from <a href="https://t.co/K8xqg9uGEI" rel="noopener noreferrer nofollow">http://rollsigngallery.com</a>, along with <a href="https://t.co/rHuq1y5Skw" rel="noopener noreferrer nofollow">http://github.com/hshutan</a> and <a href="https://t.co/diNcOBTBPe" rel="noopener noreferrer nofollow">http://github.com/alusch</a>. Without each of these individuals guides and help this project would've taken so so so much more time and may have not succeeded.</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">a video of the sign in action those of you who made it this far <a href="https://t.co/IQZypra9ZT">pic.twitter.com/IQZypra9ZT</a></p>&mdash; iain (@isiain) <a href="https://twitter.com/isiain/status/1622596894521270276?ref_src=twsrc%5Etfw">February 6, 2023</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure>]]></content:encoded></item><item><title><![CDATA[Pendant Workshop for Devcon Bogota]]></title><description><![CDATA[<figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Stop by to make a poap and zorb display at devcon floor one design hub :) Making hardware until we run out. <a href="https://twitter.com/hashtag/devcon?src=hash&amp;ref_src=twsrc%5Etfw">#devcon</a> <a href="https://t.co/qSj7jtVrQK">pic.twitter.com/qSj7jtVrQK</a></p>&mdash; iain (@isiain) <a href="https://twitter.com/isiain/status/1580270826057703431?ref_src=twsrc%5Etfw">October 12, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><p>At DEVCon6 I held a workshop at the design space to make a display using a round RP</p>]]></description><link>https://inpublic.space/pendant-workshop-for-devcon-bogota/</link><guid isPermaLink="false">6438fe5fb8a3f805e5296f61</guid><dc:creator><![CDATA[inpublic]]></dc:creator><pubDate>Fri, 14 Apr 2023 07:23:36 GMT</pubDate><media:content url="https://inpublic.space/content/images/2023/04/display-rp2040.jpg" medium="image"/><content:encoded><![CDATA[<figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><img src="https://inpublic.space/content/images/2023/04/display-rp2040.jpg" alt="Pendant Workshop for Devcon Bogota"><p lang="en" dir="ltr">Stop by to make a poap and zorb display at devcon floor one design hub :) Making hardware until we run out. <a href="https://twitter.com/hashtag/devcon?src=hash&amp;ref_src=twsrc%5Etfw">#devcon</a> <a href="https://t.co/qSj7jtVrQK">pic.twitter.com/qSj7jtVrQK</a></p>&mdash; iain (@isiain) <a href="https://twitter.com/isiain/status/1580270826057703431?ref_src=twsrc%5Etfw">October 12, 2022</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><p>At DEVCon6 I held a workshop at the design space to make a display using a round RP 2040 dev board and battery to show either your ZORB or POAP collection.</p><p>I started with <a href="https://www.waveshare.com/product/rp2040-lcd-1.28.htm">this waveshare display</a> and a small LiPo battery. Workshop attendees learned to solder a small 2-pin JST style connector for the display onto the LiPo battery learning soldering techniques and how the circuit works. </p><p>After the display was ready, I ran a script <a href="https://github.com/iainnash/devcon6-hardware-workshop">https://github.com/iainnash/devcon6-hardware-workshop</a> that downloaded the user's ZORB svg (using the <a href="https://api.zora.co/">ZORA API</a>) and converting it to the png bitmap format,  POAPs – badges collected at various crypto events (using the <a href="https://documentation.poap.tech/docs/api-access">POAP API</a>). POAPs and ZORBs are a great fit for this display because they natively are round NFTs. The script then resizes the images using <a href="https://imagemagick.org/">imagemagick</a> to the correct .bmp style bitmap format from the png format that the Adafruit Circuitpython libraries could use to display them cleanly. The RP2040 in the kit only has 4mb memory – and since we want to run this in python to allow for easy editing by workshop attendees we only had 1.5mb space left for the images to be displayed. About 10 images fit easily and for a simple slideshow of the user's latest POAPs that was pretty easy.</p><p>Overall, the workshop was successful in being not too long for attendees, having a great deliverable (a little customizable pendant that charged over USB) and showed creativity: one of the attendees crafted a smart display strap using velcro and hot glue that worked really well (though was slightly less strong in the asthetics department).</p>]]></content:encoded></item><item><title><![CDATA[ETHDenver Token Dispenser]]></title><description><![CDATA[<figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Excited that this <a href="https://twitter.com/ethdenver?ref_src=twsrc%5Etfw">@ETHDenver</a> hack is working: converting a 1990s change dispenser into an ERC20 dispenser — use BUIDLBux on <a href="https://twitter.com/myetherwallet?ref_src=twsrc%5Etfw">@myetherwallet</a> to get ETHDenver commerative tokens.<br>1 BUIDLBux is 4 physical tokens. Get them by the Buffiswag booth or the BBB Lounge. <a href="https://t.co/DKTURdxpm1">pic.twitter.com/DKTURdxpm1</a></p>&mdash; iain (@isiain) <a href="https://twitter.com/isiain/status/1632030772478525440?ref_src=twsrc%5Etfw">March 4,</a></blockquote></figure>]]></description><link>https://inpublic.space/ethdenver-token-dispenser/</link><guid isPermaLink="false">6438f737b8a3f805e5296f3c</guid><dc:creator><![CDATA[inpublic]]></dc:creator><pubDate>Fri, 14 Apr 2023 07:18:22 GMT</pubDate><media:content url="https://inpublic.space/content/images/2023/04/Screen-Shot-2023-04-14-at-4.04.11-PM.png" medium="image"/><content:encoded><![CDATA[<figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><img src="https://inpublic.space/content/images/2023/04/Screen-Shot-2023-04-14-at-4.04.11-PM.png" alt="ETHDenver Token Dispenser"><p lang="en" dir="ltr">Excited that this <a href="https://twitter.com/ethdenver?ref_src=twsrc%5Etfw">@ETHDenver</a> hack is working: converting a 1990s change dispenser into an ERC20 dispenser — use BUIDLBux on <a href="https://twitter.com/myetherwallet?ref_src=twsrc%5Etfw">@myetherwallet</a> to get ETHDenver commerative tokens.<br>1 BUIDLBux is 4 physical tokens. Get them by the Buffiswag booth or the BBB Lounge. <a href="https://t.co/DKTURdxpm1">pic.twitter.com/DKTURdxpm1</a></p>&mdash; iain (@isiain) <a href="https://twitter.com/isiain/status/1632030772478525440?ref_src=twsrc%5Etfw">March 4, 2023</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><p>At ETHDenver we wanted attendees to be able to interact with a physical currency derived from a virtual event currency.</p><p>I connected the vending machine over a pulse interface to a Raspberry PI running a docker image to read the balance of a blockchain account every 10 seconds. When the balanced increased, it dispensed the corresponding number of tokens.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/iainnash/ethdenver-iot-tokens"><div class="kg-bookmark-content"><div class="kg-bookmark-title">GitHub - iainnash/ethdenver-iot-tokens: IOT Token Dispenser Code w/ ZKSync era</div><div class="kg-bookmark-description">IOT Token Dispenser Code w/ ZKSync era. Contribute to iainnash/ethdenver-iot-tokens development by creating an account on GitHub.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://github.githubassets.com/favicons/favicon.svg" alt="ETHDenver Token Dispenser"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">iainnash</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/202057929bf6b5c0226640c58a737699031645049890ea11d4b626d02830db93/iainnash/ethdenver-iot-tokens" alt="ETHDenver Token Dispenser"></div></a></figure><p>The code is shown above. Interfacing with a machine that's 40 years old was actually pretty easy other than one issue: the number of tokens being dispensed where the machine didn't have enough time to fetch the token. The interface being write-only sadly the best fix was to increase the delay between pulses so the token vending machine was ready to dispense the tokens as needed. Ideally, the interface would be more complex to use a read line that would tell the controller once the dispenser was ready to dispense tokens.</p><p>To interface the physical device I used a simple mosfet to drive the control signal low. The interface into the machine was active low at 12v logic voltage – much higher than the Raspberry PI's interface voltage of 3.3v. In order to fix this mismatch, the Rasberry PI drove a mosfet to ground at 3.3v which then could properly sink the 12v coming from the coin dispenser.</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Testing <a href="https://t.co/xee6Hn5L60">pic.twitter.com/xee6Hn5L60</a></p>&mdash; iain (@isiain) <a href="https://twitter.com/isiain/status/1646774718182354946?ref_src=twsrc%5Etfw">April 14, 2023</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure>]]></content:encoded></item><item><title><![CDATA[GPTypewriter]]></title><description><![CDATA[<!--kg-card-begin: html--><div style="border-radius:4px;overflow:hidden;max-width:1000px;height: 800px;margin:0 auto;background-color:white"><iframe id="embed" width="100%" height="100%" frameborder="0" src="https://zora.co/editions/0x6e2e6eb7301f65d3d5cb7593b51f3f27ef67bad5/frame?padding=20px&mediaPadding=0px&showDetails=false&theme=light&showMedia=true&showCollectors=true&showMintingUI=false"></iframe></div><!--kg-card-end: html--><!--kg-card-begin: html--><br>
<br><!--kg-card-end: html--><p>One beauty of paper systems is an fully owned inbuilt record of previous conversations. In a web platform data is considered throwaway and corrections and retries are cheap.</p><p>GPTypewriter both allows people to physically browse their AI chat conversations but also provides an element of physicality and finality not typically</p>]]></description><link>https://inpublic.space/gptypewriter/</link><guid isPermaLink="false">6438c4aab8a3f805e5296f28</guid><dc:creator><![CDATA[inpublic]]></dc:creator><pubDate>Fri, 14 Apr 2023 03:16:37 GMT</pubDate><media:content url="https://inpublic.space/content/images/2023/04/bafybeidpcwsof7vjunn4tj7cztvipihgvazzn54a255ohjbavgp4rcx65i.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: html--><div style="border-radius:4px;overflow:hidden;max-width:1000px;height: 800px;margin:0 auto;background-color:white"><iframe id="embed" width="100%" height="100%" frameborder="0" src="https://zora.co/editions/0x6e2e6eb7301f65d3d5cb7593b51f3f27ef67bad5/frame?padding=20px&mediaPadding=0px&showDetails=false&theme=light&showMedia=true&showCollectors=true&showMintingUI=false"></iframe></div><!--kg-card-end: html--><!--kg-card-begin: html--><br>
<br><!--kg-card-end: html--><img src="https://inpublic.space/content/images/2023/04/bafybeidpcwsof7vjunn4tj7cztvipihgvazzn54a255ohjbavgp4rcx65i.jpg" alt="GPTypewriter"><p>One beauty of paper systems is an fully owned inbuilt record of previous conversations. In a web platform data is considered throwaway and corrections and retries are cheap.</p><p>GPTypewriter both allows people to physically browse their AI chat conversations but also provides an element of physicality and finality not typically associated with chat conversations.</p><p>The act of using paper to type the responses can be connected to the large amounts of energy used to provide the compute and data storage needs not just for these next generation large language models but also for typical cloud computing activities.</p><p>GPTypewriter uses an 80s era line continous roll paper impact printer. It pauses on each line as the results from the server are calculated. The actual system is run off of a linux python script and the keyboard shown is a standard keyboard.</p><p>Now, as a general API is available with GPT4 and 3.5 the next step would be to embed this functionality on a embedded compute device and embed that device directly into the printer for a cleaner installation experience.</p>]]></content:encoded></item><item><title><![CDATA[Tracked Token Timelocks]]></title><description><![CDATA[<p><strong>The Problem</strong></p><p>The <a href="https://twitter.com/paperclipDAO">Paperclip DAO</a> wanted to reward $CLIP to traders that brought NFT trades participating in the DAO's mission. However, we didn't want these tokens to be available to trade or on the open market until the DAO has finished the first set of trades.</p><p>If you have these</p>]]></description><link>https://inpublic.space/token-timelock/</link><guid isPermaLink="false">6122c17ca205e405a306e89e</guid><dc:creator><![CDATA[inpublic]]></dc:creator><pubDate>Sun, 22 Aug 2021 22:42:00 GMT</pubDate><media:content url="https://inpublic.space/content/images/2021/08/fly-d-art-photographer-mT7lXZPjk7U-unsplash-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://inpublic.space/content/images/2021/08/fly-d-art-photographer-mT7lXZPjk7U-unsplash-1.jpg" alt="Tracked Token Timelocks"><p><strong>The Problem</strong></p><p>The <a href="https://twitter.com/paperclipDAO">Paperclip DAO</a> wanted to reward $CLIP to traders that brought NFT trades participating in the DAO's mission. However, we didn't want these tokens to be available to trade or on the open market until the DAO has finished the first set of trades.</p><p>If you have these timelocked tokens, they should look fairly normal as a token but missing a few features. Receiving the timelocked tokens you'll see them go into your wallet balance but attempting to send them to a another wallet or approve them for trading will result in an error. The only valid operation for these tokens is to claim them after the claim period has finished: execute the claim() function when the tokens are unlocked. You can see when the unlock is set for in the getTimeUnlock function and claim your tokens after this period with the claim function. A standard front end is coming soon, but etherscan also works well to interact with the token.</p><p>We chose to reward ERC20 tokens over NFTs for this exercise since it allowed a clearer stake and fungibility of the DAO, along with the ability to use with snapshot voting, collab.land and others. However, this approach could be expanded to also work with NFTs.</p><p>The DAO wanted to trustlessly show that these token balances are owned by those who proposed the clip but couldn’t be sold or handled like a normal token until after the project swapping was over.</p><p><strong>The Solution</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://inpublic.space/content/images/2021/08/Screen-Shot-2021-08-22-at-5.38.15-PM.png" class="kg-image" alt="Tracked Token Timelocks" srcset="https://inpublic.space/content/images/size/w600/2021/08/Screen-Shot-2021-08-22-at-5.38.15-PM.png 600w, https://inpublic.space/content/images/size/w1000/2021/08/Screen-Shot-2021-08-22-at-5.38.15-PM.png 1000w, https://inpublic.space/content/images/size/w1600/2021/08/Screen-Shot-2021-08-22-at-5.38.15-PM.png 1600w, https://inpublic.space/content/images/2021/08/Screen-Shot-2021-08-22-at-5.38.15-PM.png 1996w" sizes="(min-width: 720px) 720px"><figcaption>Timelocked Paperclip Token Viewer</figcaption></figure><p>I first looked into <em><a href="https://github.com/bonesoul/zeppelin-solidity/blob/master/contracts/token/TokenTimelock.sol">Timelock.sol</a></em> from OpenZeppelin, however, we wanted to send out many grants and their Timelock contract supported only one grant address and time with no recovery method. Other grant methods required a secret hash reveal and were more complex than we needed. I decided to take inspiration from OpenZeppelin contract and build an approach that fuffilled the DAOs needs.</p><p>The original implementation of the token <a href="https://github.com/iainnash/token-timelock/blob/main/contracts/Timelock.sol">Timelock</a> was a simple custody contract that grants could be added to which would pull the number of granted tokens into the contract and keep track of the grantees. After a timestamp passed on the blockchain, the grantees could retrieve their grants. A secondary timestamp was added for the DAO to recover unclaimed tokens after a reasonable period of time in order to redistribute them as necessary (token lost seed phase, issue with claim etc.) after the grant period.</p><p>However, we wanted better discoverability and interaction with tools such as <a href="https://snapshot.org/">snapshot</a>, <a href="https://collab.land/">collab.land</a>, and <a href="https://etherscan.io/address/0xc1c75f6be64597bd618cc65b7c9e93a8a1342527#readContract">Etherscan</a> so grantees could trust that they were actually granted this token. The ERC20 standard methods and events are proxied through the Timelock interface showing as another ERC20 token prefixed with "Timelocked ". This token is completely readonly: the only transfer action that could happen with these tokens was when the grant was claimed the tokens would be burned and replaced with the underlying held asset after the grant lock expiry.</p><p><strong>What could this be useful for?</strong></p><p>This is a useful tool for startups granting tokens for contractors and as bonuses but don’t want those tokens yet on the market. These tokens can grant community access and are clearly reflected in the wallet of the user. This could also be reflected as a vesting schedule that converts the locked tokens to normal tokens on a time scale.</p><p>A community that could use these locked tokens well is <a href="https://fwb.help">FWB</a>. They organize their community into different seasons. Token timelock could allow members to timelock their profile NFTs or their membership tokens ensuring more market stability during seasons for key contributors. FWB could send the token up front to allow access to the community with a one or two season lock period. Then, the locked token from grant could be used to access initiatives and tools within the community without a concern of that token being put on the market until the token unlock. Grants can be for different amounts in the same timelock and user’s granted tokens can also be increased.</p><p>Another added benefit of creating the shadow token for this Timelock approach is that a hodler could forget about their grant but then find it in a token tracker and see that the contract has a balance and a claim for them. Additionally, since the token cannot be traded/the grant to address cannot be changed it cannot be used to participate in decentralized finance protocols or be redirected. <a href="https://blog.openzeppelin.com/bypassing-smart-contract-timelocks/">OpenZeppelin</a> explains this vulnerability of reassignable Timelocks that this contract code solves. It also increases trust in the community both on the side of organizers that liquidity will not be introduced too early but also on the receiver’s side that they can know their grant cannot be retroactively pulled by the granting party.</p><p><strong>Looking Ahead</strong></p><p><a href="https://github.com/iainnash/token-timelock">The code</a> is licensed under <a href="https://choosealicense.com/licenses/lgpl-3.0/">GPL version 3</a>. The code is available on an as-is basis with no audits or associated warranty. Using this code is entirely at your own risk. Moving ahead, if a team wants to use this code sponsoring an audit of the contracts would be a great first step on making this more production-friendly. Feel free to <a href="https://inpublic.space/token-timelock-announcement/twitter.com/isiain">reach out</a> with any questions.</p>]]></content:encoded></item><item><title><![CDATA[Distributing the TSynth Kit]]></title><description><![CDATA[<p>I’m selling a synth kit!</p><p>I saw a really well-put together project for a digital polyphonic synthesizer on Tindie recently sold by <a href="http://electrotechnique.cc/">Electrotechnique</a>. The polyphonic digital synthesizer was really polished and fully-featured plus it had a really reasonable price. I was excited to build one and play with it.</p>]]></description><link>https://inpublic.space/tsynth-at-inpublic-space/</link><guid isPermaLink="false">602164f9da911a0cc111e694</guid><category><![CDATA[TSynth]]></category><category><![CDATA[Electronics]]></category><dc:creator><![CDATA[inpublic]]></dc:creator><pubDate>Mon, 08 Feb 2021 17:09:00 GMT</pubDate><media:content url="https://inpublic.space/content/images/2021/02/cover.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://inpublic.space/content/images/2021/02/cover.jpg" alt="Distributing the TSynth Kit"><p>I’m selling a synth kit!</p><p>I saw a really well-put together project for a digital polyphonic synthesizer on Tindie recently sold by <a href="http://electrotechnique.cc/">Electrotechnique</a>. The polyphonic digital synthesizer was really polished and fully-featured plus it had a really reasonable price. I was excited to build one and play with it. Sadly, the kit was sold out. The creator of the project is based in South Korea, and he has a large customer base in the United States. I thought that I perhaps could order boards in bulk through the original designer and then source all the parts to make 10 or so kits to sell to others.</p><p>Tindie creates an unintentional drop system by sending an email to all people signed up to be notified when a product is back in stock at the same time. Say 50 people are on the waitlist, but there are only 10 items restocked. The first 10 purchasers will get the kit no matter when they signed up to be notified about restocks. Electrotechnique had 300 or so people interested in batches that he sold off around 20 or so – this isn’t scalable for that waitlist to buy a kit any time soon. We worked out a distribution deal where I would pay for a PCB order and pay him a fixed amount per board then resell those boards primarily in the US market.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://inpublic.space/content/images/2021/02/Screen-Shot-2021-02-08-at-12.09.53-PM.png" class="kg-image" alt="Distributing the TSynth Kit" srcset="https://inpublic.space/content/images/size/w600/2021/02/Screen-Shot-2021-02-08-at-12.09.53-PM.png 600w, https://inpublic.space/content/images/size/w1000/2021/02/Screen-Shot-2021-02-08-at-12.09.53-PM.png 1000w, https://inpublic.space/content/images/size/w1600/2021/02/Screen-Shot-2021-02-08-at-12.09.53-PM.png 1600w, https://inpublic.space/content/images/2021/02/Screen-Shot-2021-02-08-at-12.09.53-PM.png 2162w" sizes="(min-width: 720px) 720px"><figcaption>TSynth Product Page from Electrotechnique</figcaption></figure><p>This blog series will go through the steps I took to build this kit and get a new kit off the ground. While most of the heavy lifting of designing the product was done, the next steps of sourcing, support, shipping, documentation, and all of the mechanics of getting products to customers remained on my end.</p><h3 id="feasibility-analysis">Feasibility Analysis</h3><h3 id="is-this-even-a-good-idea">Is this even a good idea?</h3><p>I looked through the documentation of the project and created a spreadsheet of how much each of the parts would cost from the Bill of Materials (BOM) that described name-brand hardware. The cost was fairly high per unit, but still fairly viable for one-offs or prototypes. Non-name brand BOM equivalents were listed, and can save a lot of money: A standard branded USB socket can cost $1.35 at a US distributor, while you can purchase 10x of those for the same amount from a cheaper supplier. By combining the parts that did not matter and were common: Knobs (33 of them), USB sockets, and some hardware, I was able to come up with a margin that made sense for my time and hopefully a way for people to enjoy building this kit without needing to tediously source parts.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lh6.googleusercontent.com/7VfRJ2jZp1PFeLpuL7Bn6jSSr4Km455Aw85D5V5o2hLSBiBDrbFWvbZBUlk6vlKfSrwvcOYcwgEUimrLyPT5d0MtsKuk_qHWeboVFaZApPiA18MKyO3dtqzLfQJVtmbeC-oBTteo" class="kg-image" alt="Distributing the TSynth Kit"><figcaption>Part of the Initial Feasibility Spreadsheet</figcaption></figure><p>While at this point the idea seemed feasible, in retrospect I made some mistakes:</p><ol><li>I excluded some fairly expensive components that seem like details: properly-sized and finished screws, mounting hardware, and the MIDI sockets</li><li>Costs for an “additional” case option were not fully researched. While the photos didn’t show the entire case, the length of the case was 1” greater than the lower-cost typical acrylic size, meaning the cost of the material goes up by at least 2/3rds. Additionally, the 3d printed mount files looked fairly straightforward and easy to print, but they turned out to have a 7-10 hour combined print time.</li></ol><p>Lessons learned: Be careful about what you write off as straightforward or easy: if you are offering a product option make sure to do your research on every component and not assume little components or simple screws are easy to purchase. It's often better to leave it out initially. I believe including this product option was a good idea in the end and I’m glad that I was able to inspect the files and re-price the materials before listing the item.</p><p>If I had made this project a crowdfunding campaign with Electrotechnique, these problems would have compounded and delays (along with losing money on the project) likely would have resulted. Since the numbers were small, I was able to make a few mistakes and easily eat the cost in both shipping and ordering incorrect components.</p><p>Though, some things went well:</p><p>1. I was able to determine that I would at least break even if I sold all the stock for a trial run. (not including my time)</p><p>2. I included or accounted for many small costs: shipping to me as a seller, cost shipping supplies, platform and payment processing fees, taxes, cost of shipping platform.</p><p>This is a part series: </p><ol><li>Feasibility: Is this a good idea at all?</li><li>Sourcing: Purchasing parts, shipping, and finding suppliers.</li><li>Launch: Setting up a store, distribution channels</li><li>Shipping: International shipping makes things much more complicated</li><li>After-sales: Promotion, Reviews, and what's next</li></ol>]]></content:encoded></item><item><title><![CDATA[Building a Wireless Heart-Rate sensor]]></title><description><![CDATA[I really like using a wired heart rate sensor, but it's a little unwieldy for an installation environment. This post is detailing making a wireless battery-powered transmitter for these modules with the Adafruit Feather NRF.]]></description><link>https://inpublic.space/building-a-wireless-heart-rate-sensor/</link><guid isPermaLink="false">6020676970c2c647d2919f54</guid><category><![CDATA[Electronics]]></category><dc:creator><![CDATA[inpublic]]></dc:creator><pubDate>Mon, 08 Feb 2021 15:55:00 GMT</pubDate><media:content url="https://inpublic.space/content/images/2021/02/_DSC0489-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://inpublic.space/content/images/2021/02/_DSC0489-1.jpg" alt="Building a Wireless Heart-Rate sensor"><p>I really enjoy working with SeeedStudio’s <a href="https://www.seeedstudio.com/Grove-Ear-clip-Heart-Rate-Sensor.html" rel="noopener">wired ear-clip heart rate</a> sensors. However, they are wireless and fairly unwieldy to use for installations because my previous implementation of those sensors required wires to each sensor and a wire back to a host computer or device using USB.</p><p>The reason I didn’t just purchase an off-the-shelf Bluetooth heart rate monitor is that I wanted a pretty specific feature: namely, I wanted an ping from the sensor right when the heart rate was detected to allow for experiences that synchronized your heart rate with someone else’s or an sound experience that let you reflect in real time with your detected heart rate. I chose these sensors provided a fairly good pulse signal right when someone’s heart beat.</p><p>This project I did over the course of a night and the next morning, most of the time was spent figuring out the enclosure and getting the firmware uploaded to the feather board. Electrically, this was a very simple build.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://inpublic.space/content/images/2021/02/_DSC0491.jpg" class="kg-image" alt="Building a Wireless Heart-Rate sensor" srcset="https://inpublic.space/content/images/size/w600/2021/02/_DSC0491.jpg 600w, https://inpublic.space/content/images/size/w1000/2021/02/_DSC0491.jpg 1000w, https://inpublic.space/content/images/size/w1600/2021/02/_DSC0491.jpg 1600w, https://inpublic.space/content/images/size/w2400/2021/02/_DSC0491.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Old wired interface (top) / New interface (Bottom)</figcaption></figure><p>This describes the process I used to create new bluetooth-based battery-powered sensor modules. I opted to use Adafruit’s bluefruit feather line. Feather boards all include battery-charging and regulation circuitry. The sensor I’m using works with 3.3v so no power boosting is needed for this project. Feathers use a fairly common size and interface as well, simplifying building out enclosures and fit the job well! I used <a href="https://www.adafruit.com/product/4062?gclid=Cj0KCQiAvP6ABhCjARIsAH37rbSYJ2QKzFcACXpIKjRl2To5bbWY1BK8SIZicnQFUbQRLMLIW9tp-7waAgh2EALw_wcB" rel="noopener">Adafruit’s Feather NRF board</a> (make sure if you’re getting them to get the later model, I already had an earlier model so I used that but the price is the same for the newer model).</p><p>I took apart the plastic case enclosing the receiver module for the heart rate sensor. The module was held in place only by hot glue, easy to remove with a hot air gun set at a low temperature. I then cut the grove connector off of the sensor and directly soldered the pins to the feather. I chose to use GPIO 16, most of the GPIO pins except 13 would be a good choice. GPIO 13 is used for determining the battery level, and we want to use that functionality later.</p><p>I then wrapped the sensor module in Kapton tape: it’s used for insulating electronics often and is thin, easy to work with and high-temperature resistant. I used it instead of hot glue to keep a lower profile on the circuit board. The heart rate sensor board has exposed metal and so does the bluetooth module below it, some protection against shorting is needed to keep the enclosure compact.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://inpublic.space/content/images/2021/02/_DSC0486.jpg" class="kg-image" alt="Building a Wireless Heart-Rate sensor" srcset="https://inpublic.space/content/images/size/w600/2021/02/_DSC0486.jpg 600w, https://inpublic.space/content/images/size/w1000/2021/02/_DSC0486.jpg 1000w, https://inpublic.space/content/images/size/w1600/2021/02/_DSC0486.jpg 1600w, https://inpublic.space/content/images/size/w2400/2021/02/_DSC0486.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Wrapping board in Kapton tape and starting to secure to enclosure</figcaption></figure><p>I then <a href="https://a360.co/2N81HOg">updated some parameters</a> in Adafruit’s <a href="https://learn.adafruit.com/3d-printed-case-for-adafruit-feather/cad" rel="noopener">official Fusion 360 model</a> for the feather boards, lowering the height and margin sizes to something that more tightly fits this use case. I used a caliper to measure the clearance needed for the battery connectors, boards, and the headphone jack. Since there already was a perfectly-sized cutout on the other side of the board for the headphone jack, only minor sizing adjustments were needed.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://inpublic.space/content/images/2021/02/Screen-Shot-2021-02-07-at-10.51.35-AM.png" class="kg-image" alt="Building a Wireless Heart-Rate sensor" srcset="https://inpublic.space/content/images/size/w600/2021/02/Screen-Shot-2021-02-07-at-10.51.35-AM.png 600w, https://inpublic.space/content/images/size/w1000/2021/02/Screen-Shot-2021-02-07-at-10.51.35-AM.png 1000w, https://inpublic.space/content/images/size/w1600/2021/02/Screen-Shot-2021-02-07-at-10.51.35-AM.png 1600w, https://inpublic.space/content/images/2021/02/Screen-Shot-2021-02-07-at-10.51.35-AM.png 2096w" sizes="(min-width: 720px) 720px"><figcaption>Customizing Adafruit’s 3d Printed Feather case for the receiver module</figcaption></figure><p>I 3d printed the case with a stock Ender 3 Pro using Cura. I started with the main feather enclosure to ensure the board and wiring fit comfortably before printing the battery section and the top. I left Cura’s settings on a slightly lower quality mode since I didn’t mind small visual defects in the case as long as it worked. A benefit of this mode for me is that the indicator lights showed up a little through the case but dimly, which is good to know it’s working and active but also not distracting as those indicator lights can be very bright.</p><p>After 3d printing the case, I realized I didn’t have the proper mounting hardware and just went the easy route: small zip ties! The zip ties fit through all the mounting holes well and did a great job keeping everything together. Since the case was already fairly snug due to the earlier customization, everything fit well even with using zip ties. I also tacked down the heart rate sensor module with a small amount of hot glue to make sure it doesn’t slide around. Changing things tacked down with zip ties and hot glue is quite easy: just either cut the zip ties and put in new ones or slightly heat the hot glue and you can redo your fastening.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://inpublic.space/content/images/2021/02/_DSC0474.jpg" class="kg-image" alt="Building a Wireless Heart-Rate sensor" srcset="https://inpublic.space/content/images/size/w600/2021/02/_DSC0474.jpg 600w, https://inpublic.space/content/images/size/w1000/2021/02/_DSC0474.jpg 1000w, https://inpublic.space/content/images/size/w1600/2021/02/_DSC0474.jpg 1600w, https://inpublic.space/content/images/size/w2400/2021/02/_DSC0474.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Feather NRF within the 3d printed case enclosure including the heart rates sensor PCB</figcaption></figure><p>The final step here was to setup the battery. The case design had a separate lower snap enclosure for the battery and a hole to run the battery wire from the bottom separation of the case to the top. I used old batteries from drones I had lying around. One thing to be careful about is checking the polarity matches. If the battery has the wrong polarity you can carefully re-solder the wires or connector. Be sure to not plug in any incorrect polarity battery, it’s likely to damage the feather board. Be careful when working with batteries (along with capacitors and solar cells) — they’re usually energized even when sitting around and can catch fire/explode if they become accidentally shorted.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://inpublic.space/content/images/2021/02/_DSC0489.jpg" class="kg-image" alt="Building a Wireless Heart-Rate sensor" srcset="https://inpublic.space/content/images/size/w600/2021/02/_DSC0489.jpg 600w, https://inpublic.space/content/images/size/w1000/2021/02/_DSC0489.jpg 1000w, https://inpublic.space/content/images/size/w1600/2021/02/_DSC0489.jpg 1600w, https://inpublic.space/content/images/size/w2400/2021/02/_DSC0489.jpg 2400w" sizes="(min-width: 720px) 720px"><figcaption>Battery (with re-worked lead) and Feather board ready to snap onto the case</figcaption></figure><p>The final step was writing the <a href="https://gist.github.com/iainnash/71e541dbeec8a930bd9aa70af9f7c012" rel="noopener">firmware</a> to send the heart rate over BLE to the client. The architecture is a bit strange: it updates the heart rate on every beat so you can either use the system-aggregated value of heart rate or you can calculate it yourself based off the interval of the messages.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://inpublic.space/content/images/2021/02/Screen-Shot-2021-02-07-at-2.34.06-PM.png" class="kg-image" alt="Building a Wireless Heart-Rate sensor" srcset="https://inpublic.space/content/images/size/w600/2021/02/Screen-Shot-2021-02-07-at-2.34.06-PM.png 600w, https://inpublic.space/content/images/size/w1000/2021/02/Screen-Shot-2021-02-07-at-2.34.06-PM.png 1000w, https://inpublic.space/content/images/2021/02/Screen-Shot-2021-02-07-at-2.34.06-PM.png 1080w" sizes="(min-width: 720px) 720px"><figcaption>Arduino Platform Upload</figcaption></figure><p>I attempted to use PlatformIo, a general command-line tool and IDE for microcontrollers, but had issues with the uploading DFU (Device Firmware Upgrade) process. After 30 minutes of debugging, I switched over to the Arduino platform with the official Adafruit SDK where I had no issues updating the code on the board. Even though it’s not my favorite UX it’s important to know what is the best supported toolkit for your processors.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://inpublic.space/content/images/2021/02/Screen-Shot-2021-02-07-at-2.31.28-PM.png" class="kg-image" alt="Building a Wireless Heart-Rate sensor" srcset="https://inpublic.space/content/images/size/w600/2021/02/Screen-Shot-2021-02-07-at-2.31.28-PM.png 600w, https://inpublic.space/content/images/size/w1000/2021/02/Screen-Shot-2021-02-07-at-2.31.28-PM.png 1000w, https://inpublic.space/content/images/size/w1600/2021/02/Screen-Shot-2021-02-07-at-2.31.28-PM.png 1600w, https://inpublic.space/content/images/size/w2400/2021/02/Screen-Shot-2021-02-07-at-2.31.28-PM.png 2400w" sizes="(min-width: 720px) 720px"><figcaption>Connecting to the sensor with Chrome and getting the current heart rate!</figcaption></figure><p>Lessons learned:</p><ol><li>Test everything step by step: print the key part first, make everything work outside of the enclosure before mounting or soldering too much</li><li>Think about flexibility for mounting options with prototypes and test if silly ideas work before over-engineering everything</li><li>Be careful when it comes to batteries</li><li>Don’t spend too much time getting platforms to work. I knew that the platform I usually work on isn’t as well supported as another and switching to the Arduino IDE ended up saving me a lot of debugging time for this small project.</li></ol><p>All in all, not a bad project in a day.</p><p>Some small improvements I’d make moving ahead:</p><ol><li>Give 1–2 mm more clearance at the top of the case for a better fit with flexing the top cover. It works now, but is not that even</li><li>Add a toggle power switch. Right now these modules are on until they die. They don’t use much power and depending on your use case that may be fine. The feather board have a pin that if grounded shuts down the power, which when connected to a toggle switch makes a easy power switch without unplugging or messing with the battery leads.</li></ol>]]></content:encoded></item><item><title><![CDATA[How to correctly mock Moment.js/Dates in Jest]]></title><description><![CDATA[<p>Times and dates <a href="https://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time" rel="noopener nofollow">are infamously hard</a> to correctly implement in code. This makes testing date and time code correctly important. Testing allows for reasoning around logic in code and also to allow catching edge cases or errors before they impact users.</p><p>A common mistake when testing date and time code</p>]]></description><link>https://inpublic.space/mock-momentjs-dates/</link><guid isPermaLink="false">60206aeb70c2c647d2919f87</guid><dc:creator><![CDATA[inpublic]]></dc:creator><pubDate>Sun, 07 Feb 2021 22:34:46 GMT</pubDate><media:content url="https://inpublic.space/content/images/2021/02/1-W26Jdk8ZEo4QDC797b7smA-1.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://inpublic.space/content/images/2021/02/1-W26Jdk8ZEo4QDC797b7smA-1.jpeg" alt="How to correctly mock Moment.js/Dates in Jest"><p>Times and dates <a href="https://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time" rel="noopener nofollow">are infamously hard</a> to correctly implement in code. This makes testing date and time code correctly important. Testing allows for reasoning around logic in code and also to allow catching edge cases or errors before they impact users.</p><p>A common mistake when testing date and time code is to not set the current time to a static time. If code in the UI renders today’s date and is tested properly, that test that only works until the current time changes too much. Javascript exposes the built-in <code>Date</code> object which allows for retrieving the current time through construction with no arguments or a call to the <code>now() </code>property.</p><p><a href="https://momentjs.com/" rel="noopener nofollow">Moment.js</a> is a popular front-end date manipulation library that is commonly used to manipulate, load, format, and shift time. It uses an empty constructor to get the current time. Jest is often used in conjunction with Moment and React applications. Additionally, Jest snapshot testing introduces new dependencies on date and time that are important to consider. Below is an example problematic component that renders the current day:</p><!--kg-card-begin: markdown--><pre><code class="language-js">import React from 'react';
import moment from 'moment;
export function TodayIntro(name) {
  return &lt;h1&gt;hi {name}, today is {moment().format('MMM D')}&lt;/h1&gt;;
}
</code></pre>
<!--kg-card-end: markdown--><p>An initial test for the <code>TodayIntro </code>component could look like:</p><!--kg-card-begin: markdown--><pre><code class="language-js">// Fails when today is Jan 24 :(
expect(shallow(&lt;TodayIntro name=&quot;iain&quot; /&gt;).text())
  .toEqual('hi iain, today is Jan 23');
</code></pre>
<!--kg-card-end: markdown--><p>However, this test will fail on any day that is not Jan 23rd. A solution to this is to override Javascript’s date function to return a known date to work against when writing tests.</p><p>This code overrides the Date constructor to set a static “current” date:</p><!--kg-card-begin: markdown--><pre><code class="language-js">// Mock date constructor for Moment (recommended to use a library)
const nowString = '2018-02-02T20:20:20';
const MockDate = (lastDate) =&gt; (...args) =&gt;
  new lastDate(...(args.length ? args : [nowString]);
global.Date = jest.fn(MockDate(global.Date));
expect(
  shallow(&lt;TodayIntro name=&quot;iain&quot; /&gt;).text()
).toEqual('hi iain, today is Feb 2');
// Restore original object
global.Date.mockRestore();
</code></pre>
<!--kg-card-end: markdown--><p>An ineffective solution is to do the date math yourself against the current time the test is run. This is an ineffective test because you’re running the same code you’re testing to test the return value. For instance, if testing by comparing formatted dates through moment, one would not catch if the moment formatting code changes <code>MMM</code> to <code>JAN</code> instead of <code>Jan</code> .</p><!--kg-card-begin: markdown--><pre><code class="language-js">expect(
  shallow(&lt;TodayIntro name=&quot;iain&quot; /&gt;).text()
).toEqual(`hi iain, today is ${moment.format('MMM D')}`);
</code></pre>
<!--kg-card-end: markdown--><h2 id="ways-to-set-a-static-time-and-timezone-for-jest-js">Ways to set a static time and timezone for Jest/JS</h2><ol><li>Use a library to mock out Date object to return a static date and timezone (we’d recommend <a href="https://github.com/boblauer/MockDate" rel="noopener nofollow">MockDate</a> for simple cases, but read on for a breakdown of the alternatives)</li><li>Mock <code>moment().format()</code> to return a static string</li><li>Mock the <code>Date</code> constructor and <code>now()</code> function to return a static time</li></ol><!--kg-card-begin: markdown--><pre><code class="language-js">// Mocking out the Date.now() function (not used by moment)
const realDateNow = Date.now;
jest.spyOn(Date, 'now').mockImplementation(() =&gt; new Date('2019-01-01'));
// Test code here
Date.now.mockRestore();

// Mocking out Date constructor object (used by moment)
const nowString = '2018-02-02T20:20:20';
// Mock out the date object to return a mock null constructor
const MockDate = (lastDate) =&gt; (...args) =&gt;
  new lastDate(...(args.length ? args : [nowString]);
global.Date = jest.fn(MockDate(global.Date));
// Test code here
global.Date.mockRestore();

// Using mockdate library
import MockDate from 'mockdate';
MockDate.set('2018-1-1');
// Test code here
MockDate.reset();

// Using sinon library
import sinon from 'sinon';
// Mocking with Sinon (also mocks timers)
const clock = sinon.useFakeTimers(new Date('2018-01-01'));
// Restoring
clock.restore();
</code></pre>
<!--kg-card-end: markdown--><p>Using a library in this case is preferable because these libraries are well tested, do not introduce boilerplate code and handle transparently both cases dates can be created (<code>Date.now()</code> vs <code>new Date()</code> etc.). Additionally, using a library allows for easily following test code and setting a specific time per test which allows for better testing practices.</p><ul><li><code><a href="https://github.com/boblauer/MockDate" rel="noopener nofollow">MockDate</a></code> provides further functionality for time zones and is easy to use</li><li><code><a href="https://sinonjs.org/releases/v7.2.4/fake-timers/" rel="noopener nofollow">sinon</a></code> provides Date and timer (<code>setTimeout</code> etc.) mocks</li><li>Manually setting the mock can be useful in limited environments, however, can become rather complicated</li><li><code><a href="https://jasmine.github.io/" rel="noopener nofollow">jasmine</a></code> (not included in jest), comes with a <a href="https://jasmine.github.io/api/2.6/Clock.html" rel="noopener nofollow">jasmine.clock()</a></li></ul><p>The examples below use <a href="https://github.com/boblauer/MockDate" rel="noopener nofollow">MockDate</a>, which only focuses on mocking the Date object simply and handles testing time zone offsets as well for testing local time zone conversion.</p><!--kg-card-begin: markdown--><pre><code class="language-js">import ShallowRenderer from 'react-test-renderer/shallow';
import {TodayIntro} from './TodayIntro';
describe('TodayIntro', () =&gt; {
  it('should render a hello message with a date', () =&gt; {
    MockDate.set('2018-01-01');
    
    const renderer = new ShallowRenderer();
    renderer.render(&lt;TodayIntro name=&quot;test&quot; /&gt;);
    
    const result = renderer.getRenderOutput();
    expect(result.type).toBe('h1');
    expect(result.children).toBe(['hello test, today is Jan 1']);
    
    MockDate.reset();
  });
});
</code></pre>
<!--kg-card-end: markdown--><p>A <a href="https://jestjs.io/docs/en/snapshot-testing" rel="noopener nofollow">snapshot test</a>, as well, is simple to test with mocked dates:</p><!--kg-card-begin: markdown--><pre><code class="language-js">import renderer from 'react-test-renderer';
import {TodayIntro} from './TodayIntro';
describe('TodayIntro', () =&gt; {
  afterAll(() =&gt; MockDate.reset());
  it('should render a hello message with a date', () =&gt; {
    MockDate.set('2018-01-01');
    const tree = renderer.create(&lt;TodayIntro name=&quot;test&quot; /&gt;).toJSON();
    expect(tree).toMatchSnapshot();
  });
});
</code></pre>
<!--kg-card-end: markdown--><p>Since <a href="https://airbnb.io/enzyme/" rel="noopener nofollow">enzyme</a> is an awesome library, an enzyme shallow example:</p><!--kg-card-begin: markdown--><pre><code class="language-js">import {shallow} from 'enzyme';
import {TodayIntro} from './TodayIntro';
describe('TodayIntro', () =&gt; {
  afterAll(() =&gt; MockDate.reset());
  it('should render a hello message with a date', () =&gt; {
    MockDate.set('2018-01-01');
    const text = shallow(&lt;TodayIntro name=&quot;test&quot; /&gt;).text();
    expect(text).toEqual('hello test, today is Jan 1');
  });
});
</code></pre>
<!--kg-card-end: markdown--><h1 id="how-to-better-test-date-logic">How to (better) test date logic</h1><p>Dates have a lot of edge cases and logic behind them. When testing dates, make sure to cover edge cases and not just set one specific date to test and move on. Dates can also vary according to locale and time zone.</p><p>Properly testing dates require reasoning around edge cases that could occur and writing tests to ensure those edge cases behave as expected and that future changes to code or libraries used in your application don’t break those assumptions. Additionally, adding code to set the current date and time to a static date and time across all test code may be easier, but prevents good reasoning around testing Dates and hides test assumptions in library code.</p><p>Here are a few incorrect and often implicit assumptions about dates:</p><ol><li>Clients all exist within one time zone and daylight saving time</li><li>All clients exist within the developer’s time zone</li><li>The length of a Month name is relatively similar</li><li>Server clocks are always correct</li><li>The server knows the client’s timezone/time settings</li></ol><!--kg-card-begin: markdown--><pre><code class="language-js">// An example test that is more brittle than it appears
function formatUserDate(user) {
  user.localCreated = moment().utc(user.created).local();
}
class UsersList extends Component {
  componentDidMount() {
    this.setState({users:
      loadUsers(users).map((user) =&gt; formatUserDate)
    });
  }
  // Rendering implementation here
}
// Test
it('should load users correctly', () =&gt; {
  jest.spyOn(Datastore, 'loadUsers').mockImplementation(() =&gt; 
    [{name: 'test', created: '2019-01-02T20:00:00'}]
  ))});
  expect(mount(&lt;UsersList /&gt;)).toMatchSnapshot();
});
</code></pre>
<!--kg-card-end: markdown--><p>This test assumes the server is always in the correct timezone and that timezone is set correctly. Instead, set the timezone and make sure the date matches the local timezone correctly.</p><!--kg-card-begin: markdown--><pre><code class="language-js">it('should load users correctly in TZ 120 min offset', () =&gt; {
  MockDate.set('2020-02-02', 120);
  jest.spyOn(Datastore, 'loadUsers').mockImplementation(() =&gt; 
    [{name: 'test', created: '2019-01-02T20:00:00'}]
  ))});
  expect(mount(&lt;UsersList /&gt;)).toMatchSnapshot();
});
it('should load users correctly in TZ 10 min offset', () =&gt; {
  MockDate.set('2020-02-02', 10);
  jest.spyOn(Datastore, 'loadUsers').mockImplementation(() =&gt; 
    [{name: 'test', created: '2019-01-02T18:10:00'}]
  ))});
  expect(mount(&lt;UsersList /&gt;)).toMatchSnapshot();
});
</code></pre>
<!--kg-card-end: markdown--><p>It is important to ensure that when tests access the current time the “current time” is set to a static value. If the value is dynamic, either tests eventually break or a test is testing against dynamic values. Dynamic values are not effective at testing behavior since a bug will not be exposed by comparing the return value of two functions that are the same as compared to comparing to a static value that doesn’t change as the code is modified.</p><h1 id="looking-ahead-date-time-storage-and-design">Looking ahead: Date time storage and design</h1><p>Having a requirement to add tests to a code base doesn’t necessarily provide any value unless those tests are reviewed, run, and reasoned about just as strictly as running code.</p><p>Date and time logic introduces a large set of possibilities in terms of behavior and output, making a strong incentive to test effectively for date and time. Beyond testing, acknowledging and keeping relevant data along with a strategy to synchronize and store date times consistently across systems early on both helps testing and makes for a better user experience.</p><p>These tips and approaches apply to more than just Javascript &amp; Jest testing for dates and times. They also work in a NodeJS context and in a general sense around key things to test for in systems that handle date and time in general. In many cases, storing time on the server in UTC (Universal coordinated time) then converting to the local time zone based on client / browser settings is ideal. If the client is inaccessible, storing both the UTC time and user’s actual timezone is an effective way to consistently treat dates and times.</p>]]></content:encoded></item></channel></rss>