<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Brodan</title>
    <description>The latest articles on DEV Community by Brodan (@brodan).</description>
    <link>https://dev.to/brodan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3189%2F90c86b51-301c-4fb0-9674-c561c198d385.jpg</url>
      <title>DEV Community: Brodan</title>
      <link>https://dev.to/brodan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/brodan"/>
    <language>en</language>
    <item>
      <title>Learning NFT Provenance by Example: A Bored Ape Investigation</title>
      <dc:creator>Brodan</dc:creator>
      <pubDate>Wed, 23 Feb 2022 03:57:46 +0000</pubDate>
      <link>https://dev.to/brodan/learning-nft-provenance-by-example-a-bored-ape-investigation-hfe</link>
      <guid>https://dev.to/brodan/learning-nft-provenance-by-example-a-bored-ape-investigation-hfe</guid>
      <description>&lt;p&gt;NOTE: This post was originally published on my personal blog and can be found &lt;a href="https://brodan.biz/blog/learning-nft-provenance-by-example/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I was recently approached by an artist friend of mine to help them build and launch an NFT collection using their artwork. I've never worked with smart contracts or any of the NFT-related technologies, so I've been learning all of it myself. One concept that I found particularly interesting was the idea of provenance.&lt;/p&gt;

&lt;p&gt;This post will cover provenance as it pertains to NFTs. It's broken up into two sections. The first section discuses my journey of learning about and calculating the provenance of a popular NFT collection. The second section discuses the curious discrepancy I found in my calculation, how I found it, and what it could mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Provenance?
&lt;/h2&gt;

&lt;p&gt;The term "provenance" has existed long before smart contracts and NFTs. In general terms, it refers to the place of origin or earliest known history of something. The term is almost synonymous with "origin". In the crypto/NFT space, provenance could actually mean a few things, but it's &lt;em&gt;usually&lt;/em&gt; referring more specifically to a provenance hash, which is a hash of all the assets of an NFT collection before minting.&lt;/p&gt;

&lt;p&gt;The purpose of a provenance hash is to show that the assets that exist on-chain are the same assets that were initially generated. If any of the assets are manipulated in any way, it would be obvious because re-calculating the hash would result in a value different than the provenance hash.&lt;/p&gt;

&lt;p&gt;One of the current issues with a provenance hash is that there is no standardized/consistent method of calculating it, so one collection might implement it differently than the next. It's up to the NFT creators to describe how their particular provenance hash was calculated so that it can be independently verified.&lt;/p&gt;

&lt;p&gt;Armed with some conceptual knowledge of provenance, I decided to solidify my understanding of it by verifying the provenance of an existing NFT collection. I chose the &lt;a href="https://boredapeyachtclub.com/" rel="noopener noreferrer"&gt;Bored Ape Yacht Club&lt;/a&gt; (BAYC) collection to test on because of its ubiquity and because their &lt;a href="https://boredapeyachtclub.com/#/provenance" rel="noopener noreferrer"&gt;provenance information&lt;/a&gt; is available publicly on their site (many NFT collections do not provide this information).&lt;/p&gt;

&lt;h3&gt;
  
  
  How is Provenance Calculated?
&lt;/h3&gt;

&lt;p&gt;The code used in this section can be found in my &lt;a href="https://github.com/Brodan/bayc-provenance-verifier" rel="noopener noreferrer"&gt;bayc-provenance-verifier&lt;/a&gt; repo.&lt;/p&gt;

&lt;p&gt;The BAYC provenance page describes exactly how their provenance hash was calculated: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;Each Bored Ape image is firstly hashed using SHA-256 algorithm. A combined string is obtained by concatenating SHA-256 of each Bored Ape image in the specific order as listed below. The final proof is obtained by SHA-256 hashing this combined string. This is the final provenance record stored on the smart contract.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;They also provide their final proof hash of &lt;code&gt;cc354b3fcacee8844dcc9861004da081f71df9567775b3f3a43412752752c0bf&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The instructions above seemed simple enough to reproduce so I decided to do so using TypeScript. I used the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BAYCData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;provenance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tokenId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;imageHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;traits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BAYC_PROVENANCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cc354b3fcacee8844dcc9861004da081f71df9567775b3f3a43412752752c0bf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BAYC_METADATA_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://ipfs.io/ipfs/Qme57kZ2VuVzcj5sC3tVHFgyyEgBTmAnyTK45YVNxKf6hi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;responseType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;arraybuffer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashedImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hashedImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BAYC_METADATA_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;BAYCData&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="c1"&gt;// get every individual image hash&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;hashes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// make one big string and hash it&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;concatenatedHashString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hashes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;concatenatedHashString&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// check for a match&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;BAYC_PROVENANCE&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FAILURE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script can be ran using &lt;code&gt;ts-node&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As a warning: this script takes a &lt;em&gt;long&lt;/em&gt; time to run. I didn't realize just how large of a number 10,000 is, but it takes a while to make all of those requests since this is being done synchronously and in serial.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/wbXkGmM7YMgpCggnPB/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/wbXkGmM7YMgpCggnPB/giphy.gif" alt="waiting"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My first attempt at writing this script was less synchronous and used &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all" rel="noopener noreferrer"&gt;Promise.all()&lt;/a&gt; to make the individual image requests concurrently, but it would always fail around the 200th request due to IPFS gateway issues. I suspect it was because of the huge amount of simultaneous outgoing requests being made. &lt;/p&gt;

&lt;h2&gt;
  
  
  My Results
&lt;/h2&gt;

&lt;p&gt;I ran the script, waited a while, and saw the &lt;code&gt;FAILURE&lt;/code&gt; message log to my console. Then, I double checked my logic and ran it again. &lt;code&gt;FAILURE&lt;/code&gt;. I repeated this a few times. I had no idea why it wasn't working. The logic looked fine. I decided to compare the two concatenated hash strings instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding the Incorrect Hashes
&lt;/h3&gt;

&lt;p&gt;I first tried to &lt;code&gt;diff&lt;/code&gt; the concatenated hash string that I produced with the one listed on the site's provenance page, but since the output is only one line it doesn't tell me anything other than that the lines are, obviously, different.&lt;/p&gt;

&lt;p&gt;After some googling I came across the fold command which can "fold long lines for finite width output device". SHA-256 hashes are 64 hex characters long, so the &lt;code&gt;-w 64&lt;/code&gt; flag will ensure that every individual hash from the concatenated hash is split onto its own line for the diff.&lt;/p&gt;

&lt;p&gt;Now I could use the &lt;code&gt;diff&lt;/code&gt; command &lt;em&gt;and&lt;/em&gt; the &lt;code&gt;fold&lt;/code&gt; command together to figure out where the two hash strings differed:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;diff &amp;lt;(fold -w 64 my_hash.txt) &amp;lt;(fold -w 64 bayc_hash.txt)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The command above uses &lt;a href="https://tldp.org/LDP/abs/html/process-sub.html" rel="noopener noreferrer"&gt;process substitution&lt;/a&gt; to return the output of &lt;code&gt;fold&lt;/code&gt; as a file-descriptor so that it can be used as the arguments for &lt;code&gt;diff&lt;/code&gt;. Here is a snippet of what the output looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2032c2032
&amp;lt; e33123649788fb044e6d832e66231b26867af618ea80221e57166f388c2efb2f
---
&amp;gt; 5bb1c8a8390c284e9a4634c04eee34dfd08759d66d2b613b0631ab10e2f1f3d9
2034c2034
&amp;lt; 00ebcc4a1409783b3809a0ff383d38a54bc768aa6ff5490e008e788660a85fbd
---
&amp;gt; 5bb1c8a8390c284e9a4634c04eee34dfd08759d66d2b613b0631ab10e2f1f3d9
2039c2039
&amp;lt; d1ec635d95f689eb22191daba2b03efb092d1d8e56e9a09ad697f45c0dd1d014
---
&amp;gt; 5bb1c8a8390c284e9a4634c04eee34dfd08759d66d2b613b0631ab10e2f1f3d9
2041,2042c2041,2042
&amp;lt; 6c62372729385006372dd94807cbcc3d6bd3e4e6d84f349790b031de2bc318f4
&amp;lt; f0455b3cb1998c00ab6ea3bbdd74939aeaea08ab11bc79567d045e612882c6d6
---
&amp;gt; 5bb1c8a8390c284e9a4634c04eee34dfd08759d66d2b613b0631ab10e2f1f3d9
&amp;gt; 5bb1c8a8390c284e9a4634c04eee34dfd08759d66d2b613b0631ab10e2f1f3d9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is grouped together so that the line number(s) that differ are shown first, e.g. &lt;code&gt;2032c2032&lt;/code&gt; and then difference between those lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Something Suspicious
&lt;/h3&gt;

&lt;p&gt;Notice anything strange about the output above? It took me a second but then it hit me. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/iAYupOdWXQy5a4nVGk/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/iAYupOdWXQy5a4nVGk/giphy.gif" alt="perplexed gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nearly every line in the diff (31 out of 32) shows the same hash: &lt;code&gt;5bb1c8a8390c284e9a4634c04eee34dfd08759d66d2b613b0631ab10e2f1f3d9&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Don't believe me? Go to the &lt;a href="https://boredapeyachtclub.com/#/provenance" rel="noopener noreferrer"&gt;BAYC provenance page&lt;/a&gt; and &lt;code&gt;cmd + f&lt;/code&gt; the hash above. It exists 31 times in their provenance.&lt;/p&gt;

&lt;p&gt;How could that be possible? It's easy to miss something like this when glancing at the provenance page because there are 10,000 lines of hashes listed, but this is clear as day.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://www.google.com/search?q=5bb1c8a8390c284e9a4634c04eee34dfd08759d66d2b613b0631ab10e2f1f3d9" rel="noopener noreferrer"&gt;Google search&lt;/a&gt; of this hash returns only one result, which is the provenance page for another NFT project called &lt;a href="https://provenance.thebearnbear.com/" rel="noopener noreferrer"&gt;BearNBear&lt;/a&gt;. And just like the BAYC, the &lt;a href="https://ipfs.io/ipfs/QmXPzLh5CjmqSi4dDJ3mHf6JJsWJSThCJXB6Vova5VQnZp" rel="noopener noreferrer"&gt;image&lt;/a&gt; shown in that collection does not actually produce this hash when run through SHA-256. How on Earth is it possible that two completely unrelated NFT projects both mention this hash?&lt;/p&gt;

&lt;p&gt;A friend of mine, &lt;a href="https://twitter.com/1ofthemanymatts/status/1491107469070057472" rel="noopener noreferrer"&gt;Matt&lt;/a&gt;, stepped in to help figure out the significance of this hash but we remain stumped. Another colleague of mine, &lt;a href="https://twitter.com/conundrumer" rel="noopener noreferrer"&gt;@conundrumer&lt;/a&gt;, suggested that this hash may be that of a null value, but we quickly determined that &lt;a href="https://crypto.stackexchange.com/a/26135" rel="noopener noreferrer"&gt;this was not the case&lt;/a&gt; either.&lt;/p&gt;

&lt;h3&gt;
  
  
  Another Outlier
&lt;/h3&gt;

&lt;p&gt;Finally, there was one hash out of the 32 mismatches that wasn't the same value as the rest of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2306c2306
&amp;lt; d68d5402848bb7fba993411e521e1352c3446cd6e6676c95fa55d3e51061d540
---
&amp;gt; d1d7cec7d41312780f1e5064fd2d199a7bef312ec84c8ceb367005bbb26f5d7a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This, to me, is even more fishy than the others, and &lt;em&gt;could&lt;/em&gt; mean that &lt;a href="https://opensea.io/assets/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/1159" rel="noopener noreferrer"&gt;Bored Ape #1159&lt;/a&gt; was manually manipulated at some point after minting.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Does it All Mean?
&lt;/h3&gt;

&lt;p&gt;Where did these incorrect hashes come from? What image produces the hash that is listed 31 times? How has no one else noticed this before me?&lt;/p&gt;

&lt;p&gt;Frankly, I don't know the answer to any of these questions. It's possible that there were some issues that came up during BAYC's image generation but were resolved before minting began. However, it is also possible that this means that assets were manipulated at some point after being minted, which would be extremely sus.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/3gNotAoIRZsb9UHPnj/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/3gNotAoIRZsb9UHPnj/giphy.gif" alt="suspicious"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The former seems much more likely. Nevertheless, it's pretty suspicious and I really wish I could get an explanation directly from someone at Yuga Labs. It feels so wrong that I'm the first person to raise a conern about this.&lt;/p&gt;

&lt;p&gt;The Bored Apes in question, based on my calculated hashes and accounting for the starting index offset of &lt;code&gt;8853&lt;/code&gt;, are numbers &lt;code&gt;885, 887, 892, 894, 895, 897, 1087, 1089, 1091, 1094, 1109, 1111, 1112, 1113, 1118, 1120, 1121, 1126, 1129, 1134, 1136, 1140, 1142, 1153, 1155, 1159, 1199, 1200, 1303, 2183, 5250, 8817&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;I had a lot of fun learning about provenance and provenance hashes in this manner. Hopefully you found something of value in this post. If you'd like to follow along with the drama, I recently tweeted at the BAYC account in the hopes that someone in the community would see it and provide an explanation (or at least point me to someone who can). Any likes/retweets/follows are appreciated!&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1491091395645689860-372" src="https://platform.twitter.com/embed/Tweet.html?id=1491091395645689860"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1491091395645689860-372');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1491091395645689860&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>nft</category>
      <category>web3</category>
      <category>typescript</category>
    </item>
    <item>
      <title>", hopefully" - An Intro to Git Hooks</title>
      <dc:creator>Brodan</dc:creator>
      <pubDate>Thu, 06 May 2021 19:43:27 +0000</pubDate>
      <link>https://dev.to/brodan/hopefully-an-intro-to-git-hooks-507c</link>
      <guid>https://dev.to/brodan/hopefully-an-intro-to-git-hooks-507c</guid>
      <description>&lt;p&gt;In yet another instance of a blog post inspired by a single tweet, this post will offer a &lt;em&gt;very&lt;/em&gt; basic introduction to git hooks (commit hooks, to be specific).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/BkVqfREIvC012/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/BkVqfREIvC012/giphy.gif" alt="I'm hooked GIF"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The Tweet
&lt;/h1&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1389708730086957061-448" src="https://platform.twitter.com/embed/Tweet.html?id=1389708730086957061"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1389708730086957061-448');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1389708730086957061&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;I saw this on my timeline earlier today, had a good chuckle, and then realized that despite knowing &lt;em&gt;about&lt;/em&gt; git hooks for a long time, I've never actually written one. I decided to fix that by writing a dead simple hook that would recreate the aforementioned Tweet.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Code
&lt;/h1&gt;

&lt;p&gt;Start by initializing a new git repo as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /tmp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir &lt;/span&gt;hook-testing
&lt;span class="nb"&gt;cd &lt;/span&gt;hook-testing
git init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;git actually generates a number of sample hooks when you initialize a repo. You can see them all by running &lt;code&gt;ls .git/hooks/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you look closely, there's a hook named &lt;code&gt;prepare-commit-msg.sample&lt;/code&gt;. How convenient! This hook, once enabled, will run every time a &lt;code&gt;git commit&lt;/code&gt; command is run when working in this repo.&lt;/p&gt;

&lt;p&gt;You can read more about this hook in the &lt;a href="https://git-scm.com/docs/githooks#_prepare_commit_msg" rel="noopener noreferrer"&gt;githooks Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In order for git to actually pick up and run a hook, the &lt;code&gt;.sample&lt;/code&gt; extension must be removed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mv&lt;/span&gt; .git/hooks/prepare-commit-msg.sample .git/hooks/prepare-commit-msg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;.git/hooks/prepare-commit-msg&lt;/code&gt; in an editor and feel free to look at the examples. Then replace it all with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="nv"&gt;COMMIT_MSG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;COMMIT_SOURCE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;

&lt;span class="nv"&gt;DREAM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;", hopefully"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$COMMIT_SOURCE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"message"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;
&lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="nv"&gt;$COMMIT_MSG_FILE&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nv"&gt;$DREAM&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$COMMIT_MSG_FILE&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I kept this hook pretty simple since my shell-scripting abilities are lackluster.&lt;/p&gt;

&lt;p&gt;git passes three arguments into the &lt;code&gt;prepare-commit-msg&lt;/code&gt; hook, but we only care about the first two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;$1&lt;/code&gt; is the name of the file that contains the commit log message. We will append our optimistic message to this file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$2&lt;/code&gt; is the source of the commit message and is set according to how the commit is being generated (such as in a &lt;code&gt;merge&lt;/code&gt;, &lt;code&gt;squash&lt;/code&gt;, etc, or just a regular old &lt;code&gt;commit&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, the hook is only going to run if the commit source is &lt;code&gt;"message"&lt;/code&gt;, meaning that the commit was made using the &lt;code&gt;-m&lt;/code&gt; flag. Feel free to modify this to your liking.&lt;/p&gt;

&lt;p&gt;In order to see it in action, we need to commit something:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;--allow-empty&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"adding an empty commit"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;master 1031a40] adding an empty commit, hopefully
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see above, the commit message was updated to include the &lt;code&gt;", hopefully"&lt;/code&gt; message. You can run &lt;code&gt;git log&lt;/code&gt; to see it again if you want to double-check.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Conclusion
&lt;/h1&gt;

&lt;p&gt;I hope you found this post informative and entertaining. The hook itself is very simple but I actually learned a log about git internals while working on it.&lt;/p&gt;

&lt;p&gt;If you'd like to see the other posts I've written that were inspired entirely by Tweets, consider these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/blog/john-mayer-customer-service-line-twilio-voice-python" rel="noopener noreferrer"&gt;Build John Mayer's Customer Service Line with Twilio Voice and Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/brodan/waifu-mms-bot-send-a-selfie-receive-a-waifu-4617"&gt;Waifu MMS Bot - Send a Selfie, Receive a Waifu &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>git</category>
      <category>shell</category>
    </item>
    <item>
      <title>"Grab these vehicles": Send Real Simeon Texts Using Twilio Functions</title>
      <dc:creator>Brodan</dc:creator>
      <pubDate>Thu, 11 Jun 2020 04:16:45 +0000</pubDate>
      <link>https://dev.to/brodan/grab-these-vehicles-send-real-simeon-texts-using-twilio-functions-3igo</link>
      <guid>https://dev.to/brodan/grab-these-vehicles-send-real-simeon-texts-using-twilio-functions-3igo</guid>
      <description>&lt;p&gt;I started playing Grand Theft Auto Online again after putting it down for several years and I was quickly reminded of one of the most annoying features of the game: Simeon vehicles. &lt;/p&gt;

&lt;p&gt;Also known as &lt;a href="https://gta.fandom.com/wiki/Simeon_Car_Export_Requests"&gt;Simeon Car Export Requests&lt;/a&gt;, fans of the game are all too familiar with this mechanic, which has achieved meme-status due to its annoyance and inconvenience.&lt;/p&gt;

&lt;p&gt;I was inspired to build this functionality IRL to troll my GTA Online friends with real text messages. This post will quickly walk through how to build your own Simeon texting service in less than 10 minutes using &lt;a href="https://www.twilio.com/docs/runtime/functions"&gt;Twilio Functions&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Getting Started
&lt;/h1&gt;

&lt;p&gt;If you've never used Twilio before and you're interested in getting started, consider using &lt;a href="//www.twilio.com/referral/u9A86w"&gt;my referral code&lt;/a&gt; for $10 of free Twilio credit (which goes a really long way and will cover anything needed for this post).&lt;/p&gt;

&lt;p&gt;Twilio Functions allow you to quickly and easily execute JavaScript in a serverless environment in response to incoming messages or voice calls (or HTTP requests).  Functions are super extensible and can be integrated into other Twilio offerings as well, but I find them best used for quick and silly projects like this one that don't warrant an entire back-end and deployment pipeline.&lt;/p&gt;

&lt;p&gt;The first step is to buy a Twilio phone number, which is needed to send outgoing SMS messages. This can be done from the &lt;a href="https://www.twilio.com/console/phone-numbers/incoming"&gt;Twilio Console&lt;/a&gt;. More info on buying a Twilio number can be found &lt;a href="https://support.twilio.com/hc/en-us/articles/223135247-How-to-Search-for-and-Buy-a-Twilio-Phone-Number-from-Console"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Configuring the Twilio Function
&lt;/h1&gt;

&lt;p&gt;Next, navigate to the &lt;a href="https://www.twilio.com/console/functions/manage"&gt;Functions&lt;/a&gt; section of the Console and create a new function. Give the function a descriptive name and path (I've obscured mine for privacy's sake). For simplicity' sake, make sure that 'Check for valid Twilio signature' is &lt;em&gt;off&lt;/em&gt;, since requests to this Function will come from HTTP and not other Twilio services. More information about this can be found in Twilio's &lt;a href="https://www.twilio.com/docs/usage/security"&gt;Security documentation&lt;/a&gt;. The configuration should look similar to this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ir6U4Pf4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mpeenmqg2nxq2lctbo8n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir6U4Pf4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mpeenmqg2nxq2lctbo8n.png" alt="Function Configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next step is to paste in the following code into the Function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;messageOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cheval Surge, Ocelot Jackal , Obey Tailgater, Dundreary Landtstalker, Maibatsu Penumbra.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dundreary Landstalker, Maibatsu Penumbra, Ocelot F620, Fathom FQ 2, Mammoth Patriot.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dundreary Landstalker, Maibatsu Penumbra, Ocelot F620, Fathom FQ 2, Bollokan Prairie.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Karin BeeJay XL, Bravado Gresley, Albany Buccaneer, Western Daemon, Western Bagger.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ubermacht Sentinel XS, Vapid Dominator, Benefactor Schafter, Cheval Surge, Ocelot Jackal.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Benefactor Serrano, Mammoth Patriot, Emperor Habanero, Schyster Fusilade, Bravado Gresley.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fathom FQ 2, Mammoth Patriot, Emperor Habanero, Schyster Fusilade, Bravado Gresley.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;randomOption&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messageOptions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;messageOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="nx"&gt;messageBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Grab these vehicles: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;randomOption&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getTwilioClient&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SIMEON_NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;messageBody&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember to save the Function after pasting. &lt;/p&gt;

&lt;p&gt;The code above will choose a random text message body based on the possible texts that Simeon sends in-game and then send an outgoing SMS via the built-in &lt;code&gt;twilio-node&lt;/code&gt; helper library.&lt;/p&gt;

&lt;p&gt;Two of the more notable lines are &lt;code&gt;event.toNumber&lt;/code&gt; and &lt;code&gt;context.SIMEON_NUMBER&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;event.toNumber&lt;/code&gt; is looking for a query param called &lt;code&gt;toNumber&lt;/code&gt; which must be included on incoming requests (an example is shown in the next section).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;context.SIMEON_NUMBER&lt;/code&gt; is looking for an environment variable named &lt;code&gt;SIMEON_NUMBER&lt;/code&gt; configured within the Function console.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Set the Environment Variable
&lt;/h1&gt;

&lt;p&gt;The final step is to set this &lt;code&gt;SIMEON_NUMBER&lt;/code&gt; environment variable for the so that the Function can access your Twilio phone number it without having to hard-code it. Navigate to the Function &lt;a href="https://www.twilio.com/console/functions/configure"&gt;Configuration&lt;/a&gt; page and add a new environment variable named &lt;code&gt;SIMEON_NUMBER&lt;/code&gt; and set the value to the number that was purchased earlier (make sure to enter it in &lt;a href="https://www.twilio.com/docs/glossary/what-e164"&gt;E.164&lt;/a&gt; format).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HkwK9_lq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5vfn7r95q8j1dgj1xxep.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HkwK9_lq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5vfn7r95q8j1dgj1xxep.png" alt="Function Configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Testing the Twilio Function
&lt;/h1&gt;

&lt;p&gt;The way to trigger this Function is via an HTTP request. &lt;code&gt;cURL&lt;/code&gt; can be used to trigger the Function from the command line as seen here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://xxxxxx-yyyy-0000.twil.io/simeon?toNumber&lt;span class="o"&gt;=&lt;/span&gt;15555555555
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the URL with the one from your own Function and set the &lt;code&gt;toNumber&lt;/code&gt; param to the phone number you want to message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--phKqBHIB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7u65gjew36z3a8hparqs.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--phKqBHIB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/7u65gjew36z3a8hparqs.jpg" alt="SMS Screnshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping Up
&lt;/h1&gt;

&lt;p&gt;That's all there is to it. The Function can be triggered from any manner such as the command line, a Node back-end, scheduled on a cronjob, etc. The possibilities with Twilio Functions and how to trigger them are endless.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed following along with this silly attempt at video game humor. If you have similar ideas for Functions based projects post them in the comments section!&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>twilio</category>
      <category>sms</category>
      <category>gtav</category>
    </item>
    <item>
      <title>Waifu MMS Bot - Send a Selfie, Receive a Waifu</title>
      <dc:creator>Brodan</dc:creator>
      <pubDate>Fri, 01 May 2020 06:24:40 +0000</pubDate>
      <link>https://dev.to/brodan/waifu-mms-bot-send-a-selfie-receive-a-waifu-4617</link>
      <guid>https://dev.to/brodan/waifu-mms-bot-send-a-selfie-receive-a-waifu-4617</guid>
      <description>&lt;p&gt;Welp, I started this about 6 hours ago after the hackathon deadline completely sneaked up on me. In true hackathon fashion: the code ain't pretty, it was submitted an hour before the deadline, and by some miracle it works!&lt;/p&gt;

&lt;p&gt;This project was inspired by a tweet I saw this afternoon:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1250427941567094786-103" src="https://platform.twitter.com/embed/Tweet.html?id=1250427941567094786"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1250427941567094786-103');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1250427941567094786&amp;amp;theme=dark"
  }



 &lt;/p&gt;

&lt;p&gt;As well as my friend and Twilio Evangelist &lt;a href="https://dev.to/sagnew"&gt;Sam Agnew&lt;/a&gt;'s response:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1255867485640888320-714" src="https://platform.twitter.com/embed/Tweet.html?id=1255867485640888320"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1255867485640888320-714');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1255867485640888320&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;This application allows you to send a selfie to a Twilio phone number and receive and "waifu" version of it in return that was generated using a trained model. The image conversion is all handled by the &lt;a href="https://waifu.lofiu.com/index.html" rel="noopener noreferrer"&gt;Selfie 2 Waifu&lt;/a&gt; project which was built by &lt;a href="//creke.net"&gt;creke&lt;/a&gt;. I simply integrated Twilio and built the automation around it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Category Submission:
&lt;/h4&gt;

&lt;p&gt;I could only really see this project falling under either the 'Interesting Integrations' or 'Exciting X-Factors' categories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo Link
&lt;/h2&gt;

&lt;p&gt;Since I waited until (literally) the last minute to build and submit this I didn't have enough time to record a proper video demo or anything but here's a sample interaction with this app from my phone:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgdj47obe3ji0qr00ko9f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgdj47obe3ji0qr00ko9f.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also encourage anyone to fork the repo and try it out themselves!&lt;/p&gt;

&lt;h2&gt;
  
  
  Link to Code
&lt;/h2&gt;

&lt;p&gt;The code is MIT licensed and fully available on GitHub along with some (rudimentary) setup instructions.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Brodan" rel="noopener noreferrer"&gt;
        Brodan
      &lt;/a&gt; / &lt;a href="https://github.com/Brodan/waifu-mms-bot" rel="noopener noreferrer"&gt;
        waifu-mms-bot
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Generate your waifu-self using Twilio MMS
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;waifu-mms-bot&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Generate your waifu using Twilio MMS. Simply send a selfie to your Twilio number via MMS and receive your waifu in return.&lt;/p&gt;

&lt;p&gt;This project was built on April 30, 2020 for the &lt;a href="https://dev.to/devteam/announcing-the-twilio-hackathon-on-dev-2lh8" rel="nofollow"&gt;Twilio Hackathon on DEV&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is a project was built using &lt;a href="https://expressjs.com/" rel="nofollow noopener noreferrer"&gt;Express&lt;/a&gt;, &lt;a href="https://www.twilio.com/" rel="nofollow noopener noreferrer"&gt;Twilio&lt;/a&gt;, &lt;a href="https://github.com/puppeteer/puppeteer" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt;, and most importantly &lt;a href="https://waifu.lofiu.com/index.html" rel="nofollow noopener noreferrer"&gt;Selfie 2 Waifu&lt;/a&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Development&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;To run this locally app locally you'll need to do the following (replacing the values as you go):&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;git clone https://github.com/Brodan/waifu-mms-bot.git
npm install
export TWILIO_ACCOUNT_SID='XXXXXXXXXXXXXXXXXXXXXXX'
export TWILIO_AUTH_TOKEN='YYYYYYYYYYYYYYYYYY'
export TWILIO_NUMBER='+15555555555'

# run this in a in a seperate terminal
# install instructions: https://ngrok.com/download
ngrok http  5000

# copy your ngrok URL and export it
# also make sure configure your Twilio number to point to *YOUR_NGROK_URL*
export NGROK_URL=*YOUR_NGROK_URL*

npm start

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once everything is configured and running, send a selfie via MMS to the Twilio number you configured and wait a few seconds…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Brodan/waifu-mms-bot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  How I built it (what's the stack? did I run into issues or discover something new along the way?)
&lt;/h2&gt;

&lt;p&gt;I built this app using the following tools/technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://waifu.lofiu.com/index.html" rel="noopener noreferrer"&gt;Selfie 2 Waifu&lt;/a&gt; for handling the actual image processing/conversion&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.twilio.com/mms" rel="noopener noreferrer"&gt;Twilio MMS&lt;/a&gt; for sending and receiving images&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;Express&lt;/a&gt; for the back-end&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ngrok.com/" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; for my localhost tunneling so I could get up and running quickly and not worry about deploying&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/puppeteer/puppeteer" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; for headless browser automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest issue I ran into while building this app was figuring out how to automate the uploading of selfies to the Selfie 2 Waifu web app. I originally started out using an awesome tool called &lt;a href="https://github.com/getgauge/taiko" rel="noopener noreferrer"&gt;taiko&lt;/a&gt; that I usually use for these kinds of interactions. However, due to the page's file upload proccess I couldn't quite get automated uploads working. I then switched to &lt;a href="https://github.com/puppeteer/puppeteer" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; which I had never used before and luckily it was just simple to work with and was able to accomplish what I needed.&lt;/p&gt;

&lt;p&gt;The only other issue I ran into was working with asynchronous code throughout my Express server since I am a Node newbie and struggled with some of the &lt;code&gt;async/await&lt;/code&gt; syntax. This, along with the time constraints, is why the code is definitely not as clean as it could be and might mortify some of the JS experts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Resources/Info
&lt;/h2&gt;

&lt;p&gt;In reality, all the credit should for this app should go to the Selfie 2 Waifu author, &lt;a href="//creke.net"&gt;creke&lt;/a&gt;. Without their awesome app and the inspiration it caused I wouldn't have built this. &lt;/p&gt;

&lt;p&gt;Additional thanks to DEV and Twilio for running this hackathon.&lt;/p&gt;

</description>
      <category>twiliohackathon</category>
      <category>twilio</category>
      <category>node</category>
      <category>tensorflow</category>
    </item>
    <item>
      <title>Configuring a .pypirc File for Easier Python Packaging</title>
      <dc:creator>Brodan</dc:creator>
      <pubDate>Sun, 20 Jan 2019 02:25:07 +0000</pubDate>
      <link>https://dev.to/brodan/configuring-a-pypirc-file-for-easier-python-packaging-2d50</link>
      <guid>https://dev.to/brodan/configuring-a-pypirc-file-for-easier-python-packaging-2d50</guid>
      <description>&lt;p&gt;PyPI is the Python Package Index. Its purpose is to help Python developers find and install software developed by the Python community. &lt;/p&gt;

&lt;p&gt;I recently built my first Python package, &lt;a href="https://github.com/brodan/patter"&gt;patter&lt;/a&gt;, and released it publicly via PyPI. I ran into a few hiccups along the way, so I am writing this post to help those in a similar position. &lt;/p&gt;

&lt;p&gt;This post will describe the basics of a .pypirc file and how to configure and secure it. I originally wrote this post for the &lt;a href="https://truveris.github.io/"&gt;Truveris Engineering blog&lt;/a&gt; and I am reposting it here for additional reach.&lt;/p&gt;

&lt;h1&gt;
  
  
  Getting Started
&lt;/h1&gt;

&lt;p&gt;Before proceeding, it's a good idea to make sure that the &lt;code&gt;setuptools&lt;/code&gt; and &lt;code&gt;wheel&lt;/code&gt; libraries are up to date. The following command will update them if needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install -U setuptools wheel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This post will assume that you have a new Python library that is ready to be published. The source code should be packaged using a command like the one below. Your command may differ slightly depending on the needs of your package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python setup.py sdist bdist_wheel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To read more about creating a distributable Python package, see &lt;a href="https://packaging.python.org/tutorials/packaging-projects/#generating-distribution-archives"&gt;these&lt;/a&gt; docs. &lt;/p&gt;

&lt;p&gt;In the next section, I use the &lt;a href="https://github.com/pypa/twine"&gt;twine&lt;/a&gt; utility to facilitate the release of my new package. You can read about the benefits of using &lt;code&gt;twine&lt;/code&gt; over the built-in packaging tools &lt;a href="https://github.com/pypa/twine#why-should-i-use-this"&gt;here&lt;/a&gt;. Install &lt;code&gt;twine&lt;/code&gt; using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ pip install twine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  The .pypirc File
&lt;/h1&gt;

&lt;p&gt;There are two main benefits to using a &lt;code&gt;.pypirc&lt;/code&gt; file: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It removes the need to enter a username/password when pushing to PyPI. &lt;/li&gt;
&lt;li&gt;It simplifies command line usage when pushing packages to a non-default package repository (i.e. anywhere other than pypi.org). &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The official documentation on the &lt;code&gt;.pypirc&lt;/code&gt; file can be found &lt;a href="https://docs.python.org/3/distutils/packageindex.html#the-pypirc-file"&gt;here&lt;/a&gt;. The contents of my &lt;code&gt;.pypirc&lt;/code&gt; file can be seen below. This file must be placed in &lt;code&gt;$HOME/.pypirc&lt;/code&gt; for pip/twine to use it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    [distutils]
    index-servers=
        pypi
        testpypi

    [pypi]
    username: brodan
    password: xxxxxxxxxxxxxxxx

    [testpypi]
    repository: https://test.pypi.org/legacy/
    username: brodan
    password: yyyyyyyyyyyyyyyy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep in mind, &lt;a href="https://pypi.org/"&gt;pypi.org&lt;/a&gt; and &lt;a href="https://test.pypi.org/"&gt;test.pypi.org&lt;/a&gt; are not integrated, so you'll need to have a separate account created on each site. &lt;/p&gt;

&lt;p&gt;One thing to notice above is that the &lt;code&gt;[pypi]&lt;/code&gt; section does not have &lt;code&gt;repository&lt;/code&gt; configured, but the &lt;code&gt;testpypi&lt;/code&gt; section does. That is because the &lt;code&gt;repository&lt;/code&gt; variable defaults to &lt;code&gt;https://upload.pypi.org/legacy/&lt;/code&gt;, so it does not need to be included in that section.&lt;/p&gt;

&lt;h1&gt;
  
  
  Uploading Python Packages
&lt;/h1&gt;

&lt;p&gt;Once the file above is in place, the &lt;code&gt;--repository&lt;/code&gt; flag can now be used with &lt;code&gt;twine&lt;/code&gt; to specify which package repository your packages will be uploaded to: &lt;/p&gt;

&lt;p&gt;If you wish to upload a package to the TestPyPI repository, the following command should be used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ twine upload --repository testpypi dist/*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, once the package is ready to be released to the public, the following should be used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ twine upload --repository pypi dist/*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that you won't be prompted for a password when running either of the above commands. You also no longer need to copy and paste repository URLs into the terminal.&lt;/p&gt;

&lt;h1&gt;
  
  
  Securing The .pypirc File
&lt;/h1&gt;

&lt;p&gt;Since the &lt;code&gt;.pypirc&lt;/code&gt; file is storing sensitive information (i.e. passwords) in plain text, it's important to set the permissions on this file accordingly so that other users on the system can't access this file. &lt;/p&gt;

&lt;p&gt;To do this, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ chmod 600 ~/.pypirc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command above will ensure that only the file owner (which should be your own user) can read and write to this file. Additional info on file permissions in UNIX can be found &lt;a href="https://www.tutorialspoint.com/unix/unix-file-permission.htm"&gt;here&lt;/a&gt;. Thanks to &lt;a href="https://unix.stackexchange.com/questions/20776/how-do-you-protect-a-plain-text-credentials-file-with-the-username-and-password/20779#20779"&gt;this StackOverflow answer&lt;/a&gt; for help on this section.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping Up
&lt;/h1&gt;

&lt;p&gt;With a &lt;code&gt;.pypirc&lt;/code&gt; file in place the process of pushing Python packages to public repositories is now much easier. &lt;/p&gt;

&lt;p&gt;If you have any questions or feedback regarding this post, please reach out to me via &lt;a href="//mailto:christopher.hranj@gmail.com"&gt;email&lt;/a&gt; or &lt;a href="https://twitter.com/brodan_"&gt;Twitter&lt;/a&gt;. Thanks for reading!&lt;/p&gt;

</description>
      <category>python</category>
      <category>pypi</category>
      <category>packaging</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How Do I Interview Candidates as a Junior Engineer?</title>
      <dc:creator>Brodan</dc:creator>
      <pubDate>Thu, 09 Nov 2017 19:21:04 +0000</pubDate>
      <link>https://dev.to/brodan/how-do-i-interview-candidates-as-a-junior-engineer-17k</link>
      <guid>https://dev.to/brodan/how-do-i-interview-candidates-as-a-junior-engineer-17k</guid>
      <description>&lt;p&gt;I'm a junior engineer and I've been at my first full-time gig for ~7 months now. My boss/CTO asked me if I'd want to start interviewing new engineering candidates and I expressed interest, but now I'm starting to worry that I'm not qualified to interview anyone since I'm just starting my career. &lt;/p&gt;

&lt;p&gt;What should be expected of me as an interviewer and how can I prepare myself to interview candidates who are anywhere from junior-senior level?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>interview</category>
    </item>
  </channel>
</rss>
