The majority of the Deep Water Studios involvement in the Sausagers NFT mint has wrapped up, and I thought it might be nice to do a half-summary half-postmortem on our involvement in the project. It was an interesting journey; I learned some new stuff, and relearned some old lessons I thought I already knew.
Backstory
Some time in 2022 it became fashionable to take ‘dead’ NFT projects and try to revive them. The first (highly successful) one I’m aware of was Ferdy Fish. More recently the Zebruhs project has been somewhat successfully rebooted.
In late 2022 the infamous @iwillpat decided to try and reboot a dead free-mint NFT project from the early 2022 Avax free mint season, Sausages Degen.
After achieving some traction, his twisted and disturbed mind dreamed up the idea of a spiritual successor to the Sausages Degen project, but focused on even more horrifying humanoid sausages.
Sausagers
Now we have samples of these monstrous creations, the underlying lore of ‘No Buns’, and an ever-increasing set of sausage related memes. Somehow the combination of a short catchphrase and deliberately weird artwork seems to resonate with people. It probably helps that Will is a dedicated hype man.
Behind the scenes, artist Furkan is furiously working on creating memes and traits for the collection. I didn’t really have any visibility into this part of the process, but I did receive and have to deal with assembling these traits into their final forms, and I’m certain that it was a crazy autistic orgy of creative expression.
Eventually it launched, and everyone got to see the final result of this weird journey. Although originally and individually the NFTs horrified me, repeated exposure seems to numb that portion of the brain, and results in a kind of detached fondness for them. The uniques in the collection are honestly amazing though.
Lets step back to the beginning, and I’ll walk through how we got involved, how it went, and what we’d do differently in the future.
Starting the project
Will approached us earlier this year with the initial proposal to do a mint for Sausagers, following the ‘t00b → y00t’ model of minting.
The mint goals seemed a bit optimistic for avax, but the work on our side didn’t seem too bad for the pay, so we agreed to take on the dev work for the project.
Requirements
We mostly discussed requirements over Discord, although Will did provide a Google doc with some specs written out for the contracts. There was a fair amount of back and forth, including emergent requirements as we tried to nail down specifics.
For example, a big part of the y00ts reveal was the fact that people were keeping track of the unminted ones. After some discussion, we were asked to add a technological measure to prevent this, a ‘reveal server’ that hides the metadata/images until they’re actually minted.
And we discovered a requirement to support multiple ‘runs’ with randomly revealed images within specific token ranges, which is a bit tricky to do properly unless planned for in advance.
Image generation
One thing that we did not get a good set of requirements for up front, was how to compose the images from the raw components. And this turned out to be quite complicated.
One of the most commonly used pieces of software for generating NFTs is the Hashlips Art Engine. Unfortunately, it provides a pretty simplistic configuration interface. I suspect that people must do a lot of art work specifically to accommodate these limitations, or that they just don’t have as complex requirements as Sausagers.
After reviewing the raw traits provided, I ended up creating a 5 page document of questions for Will to answer, and compressing the requirements embedded into the traits into a tree of valid configurations.
I also ended up doing a fair amount of custom modifications to the codebase, on top of custom preprocessing and postprocessing scripts. Some of the things I was surprised to not see supported; e.g. the casing needed to be selected (if included, not all Sausagers have casing) based on the body selection and you can’t do that out of the box.
Other things were more weird and difficult. E.g. some eyes have eyelids, and the eyelids need to match the body type if an eye with eyelids is selected. But the eyelids aren’t a separate category.
That one was kind of hard to confirm was working properly, because the eyelids are so small and honestly you can barely even tell they’re there. Freaking perfectionist artists.
It took quite a few iterations working to confirm what the final set of compatible layers were, and to tweak certain layers to be more compatible. Along the way, we generated plenty of test failures like this one.
Plenty of hilarious little mistakes as well, some of which we deliberately left in.
Even after all the work put into making these things generate right, there were still a bunch of ‘bad’ combinations. Instead of slaving over fixing them all, I just generated twice the required amount and dumped them on Will to manually select from. Then I used another post-processing script to shuffle them and renumber them to match the run size.
The amount of details and options that Will and Furkan created are legitimately nuts. It made this a huge pain in the ass to generate, for sure. But you can tell the love that they put into these horrifying sausage persons from the result.
Smart contracts
We ended up with four smart contracts for this project:
Condimints - mint these for 2A, exchange later for a Sausager.
Sausagers - admin / operator mint only contract that represents a Sausager.
SausageStand - an operator on the Sausagers contract; can burn a Condmint and randomly reveal a Sausager in the range of 1-5000. Designed so that new SausageStands can be created to expand the run of Sausagers in the future.
SausagersHonoraries - separate admin-minted NFT for honoraries, outside the main collection.
Nothing too exciting in these contracts. Since we randomized the Sausagers and hid them behind a reveal server, we opted to use ‘good enough’ on-chain randomness instead of Chainlink.
The main complication was actually the requirement to limit the first ‘run’ of Sausagers to the token IDs of 1-5000. Since there’s no guarantee that all Condimints would be exchanged before another run printed, Smitty agonized quite a bit over how to do this properly.
Building the UI
This was a tough one. We had a hard deadline to launch, announced in advance, and the image generation ended up taking way too much time. We didn’t even have finalized art until two days before the reveal.
It really came down very close to the wire. It made me really uncomfy to be making changes literally an hour before launch.
Launching
We had done pretty extensive testing on Fuji and had checklists and scripts for the various operations that needed to happen for the launch. Putting the contracts live and setting up the production UI was very straightforward given our preparation.
There were a few minor issues noted after launch that required some quick patches, but ultimately things went relatively smoothly considering the complexity of the setup.
Mistakes were made
I glossed over a lot of issues above, but this section is the mea culpa for all the issues we encountered along the way. Believe it or not, in my day job I’m actually an experienced lead engineer, but somehow on this project I managed to violate a ton of best practices I’ve learned along the way. Hopefully this postmortem will help me remember not to make those mistakes next time.
Requirements
We asked for, and received a requirements document pretty early on. It covered a lot of the details about the contracts, although it wasn’t comprehensive enough to work on without further discussion. It was enough to get started on though, and I felt as if the back and forth required to nail down the remaining specifics was acceptable.
The only missing thing at the time was that the specifics for how the images were to be generated were completely missing. At the time my thought was ‘how hard could it be?’ But I was so wrong. Should have gotten more requirements up front.
Scope expansion
On top of missing requirements, being the good engineers that we are, we suggested things that seemed to be missing from the project to help determine the correct final requirements. As an engineer, your goal is to give the customer what they need, not what they ask for.
But several of these things increased the scope of work a fair bit, including the reveal server, and the specifics for how multiple ‘runs’ would work. This happened, of course, after settling on payment and timeline.
Probably a bad choice to volunteer to increase the scope without accounting for it. Unlike our day jobs, we’re not on salary here.
Resourcing
Early on in the project, our dedicated web dev (VirtualQuery) backed out due to real-world plans conflicting with the intended launch timeframe.
It was still early at this point, and in retrospect we should have declined the work. Smitty and I discussed and thought that we would be able to handle the additional tasks, but in retrospect, neither of us are sufficiently good web developers and just being able to offload the responsibility to another person would have been a huge help.
It still probably would have been fine, if the other tasks weren’t as complex as they ended up being. We just didn’t have enough slack to complete all the work without crunch time.
Scheduling
Early on in the project, dates like ‘at the end of April’ were floated but it wasn’t clear that the goal was to specifically time launch/reveal around the Avax Summit.
We did an OK job of getting the contracts and the reveal server work completed concurrently and prior to the art being delivered, but the art was delivered pretty late in the timeline, and the art generation took up a considerable amount of linear time. This meant that we had left the UI work, arguably the thing we are worst at, until the very last moment.
If you’re bad at something, you’re bad at estimating how much effort it will take to get done. And in this case, it took an enormous amount of effort to get the various pages set up and tested.
Things came down VERY close to the launch time. I was sweating bullets.
Testing
Kind of a mixed bag on this project. On one hand, we managed to test a lot of stuff:
Unit tests for the smart contracts (that found bugs!)
Load testing for the UI
Manually testing the page for a lot of scenarios
Tested all ops scripts on Fuji
On the other hand, you can’t really call something tested if you’re making tweaks literally a half hour before launch. I think the failure here is primarily around scheduling. We didn’t even have sufficient time to have the customer test thoroughly before milestones (Condimints launch, Sausager reveal) and instead we made tweaks after launch.
I think the key note here is to do better on scheduling dev work and launches to allow sufficient slack for testing. Really should not be announcing firm dates until the work is 95% complete.
We also ran into extremely last minute issues related to CORS that would have been caught by testing via a full-fledged website instead of half-assing it with a S3-bucket backed website.
And finally, a mainnet full test would have been spectacular. Fuji comes with its own set of issues and isn’t representative of the final experience.
Launch
As noted, we didn’t have what I considered sufficient slack to test and refine any of the launch items. Things worked remarkably smoothly, although we did have a few glitches, including:
When we started burning Condimints, the ‘total supply’ of Condimints went down (as you would expect). But we were displaying ‘total supply / max supply’ for the mint count. Ended up adding back in the ‘minted’ amount from the SausageStand.
The on-chain randomness we used seems like it doesn’t play well with Metamask gas estimation, and some users experienced failures to reveal. We ended up hardcoding gas as a workaround.
The ‘success’ notification used the current value of the ‘quantity’ selected, so if you adjusted it while the mint was under way, it could say you minted more/less than you did. Oops.
Just in general I wasn’t happy about the quality of the status change indicators and error reporting we managed to implement. Didn’t have enough time remaining to do better.
There were a bunch of small, post-launch ideas to improve the site that we could have nailed down in advance, e.g. directly linking back and forth between the mint and reveal pages.
Payment
For the amount of work originally discussed, I thought the payment was sufficient. But we ended up gradually creep scoping the work upwards, and even the amount of effort required to do the original work ballooned in sized.
We didn’t have any process or rationale in place for how to negotiate higher fees, or even how to decide when the scope had increased enough to require charging more.
Fortunately Will is a good guy and unilaterally decided to pay more for our services once it became clear that this was a lot more work than originally anticipated. But that’s not really the kind of position you want to be in, as a developer doing contract work.
If we take on more tasks in the future, we’ll be sure to be more precise about the work we’re going to deliver for the agreed price, and include details about what kind of scope increases will trigger renegotiation for payment.
Additionally the agreement was for payment post-mint, with no specific dates or anything. We weren’t really concerned due to the reputation of the client, but it’s just not a good policy to operate that way; sooner or later you get burned.
Metamask
Metamask sucks dick. So much dick. Champion dick sucker application.
Unfortunately Rabby doesn’t support Fuji (the Avalanche testnet), and I didn’t feel comfortable switching to Core at the last minute. I will definitely either use Core or an alternative for testing next time.
Wrapping up
It certainly was an interesting project to work on. I had the opportunity to learn some new skills, and to make a little extra money on the side. The crunch time was rough, but nothing I haven’t done before.
There are never enough hours in the day though. Working on this project forced me to put aside other work, and I’m way behind on the articles I planned to have written by this point. Trying to catch up over the next week or so, which is pushing back other work for Ferdy that I had planned.
We’ll probably take on more contract work in the future, but we’ll definitely be more careful how we handle the job evaluation and scheduling to avoid some of the pitfalls we encountered on this project.