<?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>The Interledger Community 🌱: Qwyre</title>
    <description>The latest articles on The Interledger Community 🌱 by Qwyre (@qwyre).</description>
    <link>https://community.interledger.org/qwyre</link>
    <image>
      <url>https://community.interledger.org/images/yZjtw57ylgaAWxDIoXOJEiD-Gamj-lJ1sR8syXheLXw/rs:fill:90:90/g:sm/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL29yZ2Fu/aXphdGlvbi9wcm9m/aWxlX2ltYWdlLzEz/NS8wOTNlNDc3Zi1h/ZGI3LTQ2OGYtYjcw/Ni1jMzg5OGU1ZDM3/OTYucG5n</url>
      <title>The Interledger Community 🌱: Qwyre</title>
      <link>https://community.interledger.org/qwyre</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://community.interledger.org/feed/qwyre"/>
    <language>en</language>
    <item>
      <title>'Empathy' - new African speculative fiction by Cheryl Ntumy on Qwyre.com</title>
      <dc:creator>Gavin Chait</dc:creator>
      <pubDate>Mon, 09 Jan 2023 08:24:07 +0000</pubDate>
      <link>https://community.interledger.org/qwyre/empathy-new-african-speculative-fiction-by-cheryl-ntumy-on-qwyrecom-1fa1</link>
      <guid>https://community.interledger.org/qwyre/empathy-new-african-speculative-fiction-by-cheryl-ntumy-on-qwyrecom-1fa1</guid>
      <description>&lt;p&gt;&lt;a href="https://qwyre.com" rel="noopener noreferrer"&gt;Qwyre&lt;/a&gt; is starting 2023 with the release of &lt;a href="https://qwyre.com/stream/bdce8427-c0b0-4fa5-9892-902ff34e88d4" rel="noopener noreferrer"&gt;&lt;strong&gt;Empathy&lt;/strong&gt;&lt;/a&gt;, a new work of African speculative fiction by Ghanaian writer, Cheryl Ntumy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/BDRU0cpmvZwNTit9mwgDCC87pCxgWCGesDl60eoGays/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzE3M3RwZWxt/ZDZrdWIycDhwbjdt/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/BDRU0cpmvZwNTit9mwgDCC87pCxgWCGesDl60eoGays/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzE3M3RwZWxt/ZDZrdWIycDhwbjdt/LmpwZw" alt="Cover for 'Empathy' of a pink rose fading into a pure black background" width="78" height="125"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In &lt;em&gt;Empathy&lt;/em&gt;, a mother tries to save her son from himself in this haunting tale set at the brutal intersection of alien intelligence and humanity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cheryl's work is monetised and you can &lt;a href="https://qwyre.com/stream/bdce8427-c0b0-4fa5-9892-902ff34e88d4" rel="noopener noreferrer"&gt;read it here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qwyre.com" rel="noopener noreferrer"&gt;Qwyre&lt;/a&gt; is a streaming payments platform for collaborative publishing, and an ereader, and received a grant from the &lt;em&gt;Interledger Foundation&lt;/em&gt;. Tap left or right on the page to go forwards or back.&lt;/p&gt;

</description>
      <category>publishing</category>
      <category>scifi</category>
      <category>artificialintelligence</category>
      <category>ai</category>
    </item>
    <item>
      <title>Qwyre.com - What we learned, and our ethical foundations and plans for future development [Grant Report #2]</title>
      <dc:creator>Gavin Chait</dc:creator>
      <pubDate>Tue, 29 Mar 2022 09:19:02 +0000</pubDate>
      <link>https://community.interledger.org/qwyre/qwyrecom-what-we-learned-and-our-ethical-foundations-and-plans-for-future-development-grant-report-2-4kof</link>
      <guid>https://community.interledger.org/qwyre/qwyrecom-what-we-learned-and-our-ethical-foundations-and-plans-for-future-development-grant-report-2-4kof</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I'll be sharing these experiences during April's Grant for the Web Community Call. Please come along and let me know what you think.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Register here&lt;/strong&gt; for the &lt;a href="https://www.eventbrite.com/e/grant-for-the-web-project-skill-share-community-event-tickets-310769438757" rel="noopener noreferrer"&gt;Grant for the Web Project Skill Share Community Event&lt;/a&gt; 10am EST / 4pm CEST on Monday 25 April&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Streaming micropayments - web monetization - is new. And the online new is no longer absorbed into society with the unquestioning rapture of the 1990s. Neither should it.&lt;/p&gt;

&lt;p&gt;From &lt;a href="https://bfi.uchicago.edu/wp-content/uploads/Gandal-Neil-etal-An-examination-of-the-cryptocurrency-pump-and-dump-ecosystem.pdf" rel="noopener noreferrer"&gt;cryptocurrency pump and dump fraud&lt;/a&gt; to &lt;a href="https://en.wikipedia.org/wiki/Theranos" rel="noopener noreferrer"&gt;wildly hyperbolic promises&lt;/a&gt; to the tech industry's &lt;a href="https://www.theguardian.com/technology/2021/dec/06/rohingya-sue-facebook-myanmar-genocide-us-uk-legal-action-social-media-violence" rel="noopener noreferrer"&gt;complicity in genocide&lt;/a&gt; ... there are many reasons for the average person to be sceptical of the new.&lt;/p&gt;

&lt;p&gt;So, while the &lt;a href="https://community.webmonetization.org/qwyre/qwyre-com-a-guide-for-integrating-coil-web-monetization-apis-for-creatives-doing-the-job-once-1f56" rel="noopener noreferrer"&gt;technical challenges&lt;/a&gt; are great, the social ones - of building trust, resilience, and engagement - are even greater.&lt;/p&gt;

&lt;p&gt;Yet the promise of disintermediated micropayments is greater still. It is up to us, the community of web monetization makers, to earn that trust.&lt;/p&gt;

&lt;p&gt;What follows may seem unduly negative, but it helps to take stock. To have a realistic appraisal of what challenges us as we take our first tentative steps in this new medium. And, mostly, for me to recognise what I got right, wrong, and still have to reckon with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Makers are not speculators. Risk is not theirs to take.
&lt;/h2&gt;

&lt;p&gt;I used to run a small development project in the shanty towns around the formal neighborhoods of Cape Town. An ubiquitous feature of every street are tiny spaza kiosks selling small packets of sugar, soap, coffee or whatever, repackaged from much larger boxes. What is in those packets isn't some no-name brand, cheapest of the cheap. Nope. Brands. Omo. Lux. Five Roses. Hulett. All the comforting South African logos.&lt;/p&gt;

&lt;p&gt;For a simple reason.&lt;/p&gt;

&lt;p&gt;When you are poor you have no room for risk. Too much goes wrong on a daily basis for which you have no protection. You cannot afford to buy the &lt;em&gt;wrong&lt;/em&gt; brand of soap.&lt;/p&gt;

&lt;p&gt;Creators on the internet may not be quite as poor, but they're on the cusp. Most are not superstars, but have multiple sources of income, cross-subsidising their creative pursuits with whatever &lt;em&gt;ad hoc&lt;/em&gt; work they can take. Setting up &lt;a href="https://qwyre.com" rel="noopener noreferrer"&gt;Qwyre.com&lt;/a&gt; and telling authors, "Hey, come publish here, you'll get paid in real-time and direct to your payment pointer." isn't a quick win.&lt;/p&gt;

&lt;p&gt;Sampling the writing community yielded responses like, "Cryptocurrency? Hell no!"&lt;/p&gt;

&lt;p&gt;It doesn't matter that publishing on Qwyre is simpler than publishing on Amazon, there is no halo effect from established brands. Payments aren't via Visa or Mastercard. Creators can't use their own credit cards, Stripe or PayPal accounts. &lt;/p&gt;

&lt;p&gt;Instead, creators have to sign up for a whole new "wallet" and generate a "pointer" on a web service replete with promotion of speculative cryptocurrency investment.&lt;/p&gt;

&lt;p&gt;It certainly helps that Uphold and Gatehub are working on complying with regulatory authorities in the US and EU, but that takes time. It's also happening now, so when people do go to sign up, they're sometimes told that new accounts aren't available in their region while this regulatory process unfolds.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Lessons:&lt;/strong&gt; I made the technical process of uploading creative work and publishing as easy as possible, but payment pointers and getting paid are - like most publishing platforms - not part of the core workflow. It's not a technical challenge, but a critical part of their user experience.&lt;/p&gt;

&lt;p&gt;I will be redeveloping the publishing experience to integrate payment information and ensure writers know I take their getting paid as equal to them publishing and being read.&lt;/p&gt;




&lt;h2&gt;
  
  
  Making monopolised proprietary lock-in impossible
&lt;/h2&gt;

&lt;p&gt;The tech industry has awoken to &lt;a href="https://www.theverge.com/2022/3/28/23000148/eu-dma-damage-whatsapp-encryption-privacy" rel="noopener noreferrer"&gt;new EU legislation which mandates prying open the locked-down world of text apps&lt;/a&gt;, like WhatsApp. Such lockin should never have been tolerated in the first place, and a world in which the underlying technology had been designed around open interoperability protocols wouldn't have supported tech monopolies in quite the same way, or caused such pandemonium when eventually dismantled. After all, we take it for granted you can phone anyone on any network.&lt;/p&gt;

&lt;p&gt;But just because we came up with a new way to support micropayments doesn't mean we haven't stored up a future where a small number of companies monopolise monetization.&lt;/p&gt;

&lt;p&gt;As Eric Parker, South African franchise guru and co-founder of Nandos, used to say, "Run your small company as if you were a big company." &lt;/p&gt;

&lt;p&gt;He didn't mean "exploit your workers, offshore your profits, and lobby politicians". What he was talking about was technical and management debt. That the mistakes you make in the early days become part of the foundation of your company and impossible to remove later on. They will hurt you forever, and most are avoidable. If you hire only white males from the same schools at the beginning, and twenty years later you wonder how you have fomented a bro-led toxic culture hostile to women and diversity, you're being deliberately ignorant of the world around you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To summarise the summary: &lt;strong&gt;Start as you mean to go on.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In our case that means &lt;strong&gt;make monopolies impossible from the beginning&lt;/strong&gt;. Don't give yourself the option. Make it easy for your users to leave. Let them take their stuff and go whenever they want.&lt;/p&gt;

&lt;p&gt;I can't do much about how Coil, Uphold or Gatehub may choose to run their businesses, for good or ill. But I can control my own.&lt;/p&gt;

&lt;p&gt;That means make leaving easy. Creators must be able to take their work - their epubs - and publish them elsewhere. There must never be the option for me to centralise payments. Creators must always be paid direct. But these are relatively easy. There's harder stuff on interoperability I'm not sure how to solve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you have a successful link to your work, how do you take that with you? Probably impossible, unless I figure out how authors can use their own domain names?&lt;/li&gt;
&lt;li&gt;What happens if someone already has their own website but wants to integrate with mine for search and reading interface, is that possible?&lt;/li&gt;
&lt;li&gt;What about other platforms doing the same thing? Should we have shared APIs and expose each other's work? Should a reader on Qwyre be able to read a work on my platform on a different app?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you think about Amazon, or Spotify, your library isn't available on other platforms. Even when you outright buy the work, you're locked in to reading or listening on their app. I have multiple copies of some things simply because of this lack of interoperability. And it's not right.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Lessons:&lt;/strong&gt; It's easy to get lost in the technical challenge of making interoperability work, but that's to lose sight of needing to have enough readers and creators to make this necessary in the first place. &lt;em&gt;Start as you mean to go on&lt;/em&gt; doesn't mean &lt;em&gt;do it now&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So ... this is a commitment to making interoperability core to Qwyre - to using open standards (like &lt;em&gt;epub&lt;/em&gt; and not inventing some alternative locked-down &lt;em&gt;mobi&lt;/em&gt; standard instead) - but not to be so obsessed with this that it overwhelms a primary need to build a community of happy readers and creators in the first place.&lt;/p&gt;

&lt;p&gt;Crucially, it is also to be a vocal champion against monopolies, and for open interoperability. To be mindful of what I make, and what I do, to ensure I am in accord with these standards.&lt;/p&gt;




&lt;h2&gt;
  
  
  Patience and the new
&lt;/h2&gt;

&lt;p&gt;One challenge of engaging with tech-driven innovation is watching the wave lift boats all around you, but not yours. Too early, too late, not in the right place ... you end up obsessively running weird - and unnecessary - A/B testing. What about this colour? If the button was 2mm higher on the page? How about a different font?&lt;/p&gt;

&lt;p&gt;Before we had the monopoly stacks, I remember grazing widely on search engines and news portals. We certainly have a winner-takes-all problem, but see my previous point.&lt;/p&gt;

&lt;p&gt;It will take time to build confidence. Time for the various tech components to mature. Time to become part of the world.&lt;/p&gt;

&lt;p&gt;So, what should you be doing in the absence of users. What should I be doing in the absence of readers and creators?&lt;/p&gt;

&lt;p&gt;I'm running a &lt;a href="https://qwyre.com/short-story-contest" rel="noopener noreferrer"&gt;short-story competition&lt;/a&gt; although the pushback from communities where I've sought to promote it has been weird. It's not ok to promote it because of the payments process. Promoting writers to publish because they will get paid by readers while they read is, somehow, not acceptable, but charging readers for a published work and not paying the writer a live share of that is traditional and acceptable.&lt;/p&gt;

&lt;p&gt;Until monetization becomes as mainstream as credit cards, it's going to be difficult for it to be an innocuous and inoffensive way of simply getting paid.&lt;/p&gt;

&lt;p&gt;So, however long it takes to attract other writers and readers is going to take patience. Whether I like it or not doesn't enter into it.&lt;/p&gt;

&lt;p&gt;But ... I am a writer. I can write for my own platform, and that will help me improve the app in measurable ways that make writing and publishing more pleasing for others.&lt;/p&gt;

&lt;p&gt;You can see my doing that now. Here's a short story called &lt;a href="https://qwyre.com/?import=https%3A%2F%2Fqwyre.com%2Fif-only-they-couldn%27t-talk.epub" rel="noopener noreferrer"&gt;"If only they couldn't talk"&lt;/a&gt;. Go ahead. It's free. You don't even need to enable monetization to read it.&lt;/p&gt;

&lt;p&gt;Similarly, I need to engage, as much as this navel-gazing introvert is capable of. &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Lessons:&lt;/strong&gt; It's easy to over-think slow-uptake as indicative of commercial failure, but in a new industry - where so many of the concepts and technology are still very immature - it may simply be that you're too early. Spending emotional and financial bandwidth leaping about trying different things may leave you burned-out and spent. &lt;/p&gt;

&lt;p&gt;It is better to have a consistent plan, one that fits within the limits of your mental and financial health, and keep going. There are certainly examples of overnight wonders, but there are just as many of plodders who got there slow and steady.&lt;/p&gt;

&lt;p&gt;I commit to writing more, promoting more (but not obsessing about likes or hits ... I'm not particularly good at social media), and using the awesome thing I made.&lt;/p&gt;




&lt;p&gt;Being supported by Grant for the Web has been an amazing and creative experience. I've enjoyed the community, feedback, and support from the team. I know this is only the beginning, and I hope the alumni of these first few years get to gather in a few years time and look back in wonder and gratitude at all we achieved.&lt;/p&gt;

&lt;p&gt;Good luck, and go read something (on &lt;a href="https://qwyre.com/?import=https%3A%2F%2Fqwyre.com%2Fif-only-they-couldn%27t-talk.epub" rel="noopener noreferrer"&gt;Qwyre.com&lt;/a&gt;).&lt;/p&gt;

</description>
      <category>grantreports</category>
      <category>publishing</category>
      <category>ethics</category>
      <category>scifi</category>
    </item>
    <item>
      <title>Qwyre launches its first web-monetized African speculative fiction short-story competition</title>
      <dc:creator>Gavin Chait</dc:creator>
      <pubDate>Tue, 22 Mar 2022 14:57:12 +0000</pubDate>
      <link>https://community.interledger.org/qwyre/qwyre-launches-its-first-web-monetized-african-speculative-fiction-short-story-competition-134b</link>
      <guid>https://community.interledger.org/qwyre/qwyre-launches-its-first-web-monetized-african-speculative-fiction-short-story-competition-134b</guid>
      <description>&lt;p&gt;African speculative fiction is finally achieving recognition. Nnedi Okorafor and Tade Thompson have won all the awards. All of them. And amazing anthologies like &lt;em&gt;Dominion&lt;/em&gt; edited by Oghenechovwe Donald Ekpeki and Zelda Knight have introduced some amazing authors, like Dilman Dila and Mame Bougouma Diene, to a wider audience.&lt;/p&gt;

&lt;p&gt;We can do more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qwyre.com" rel="noopener noreferrer"&gt;Qwyre.com&lt;/a&gt; is live and we want to showcase awesome writers while also promoting web monetization for authors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/Xqw5aG00VRUH_O1UVH8QZ0RzTKUDFoqFmPsrfrbEAhY/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL3R0dTlrZTN3/eXpsMW1rNnE4cmht/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/Xqw5aG00VRUH_O1UVH8QZ0RzTKUDFoqFmPsrfrbEAhY/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL3R0dTlrZTN3/eXpsMW1rNnE4cmht/LmpwZw" alt="Qwyre.com landing page and navigation bar" width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are offering $50 to each of the ten best African speculative fiction short-stories published on Qwyre.com by 30 April 2022.&lt;/p&gt;

&lt;p&gt;The rules are simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5,000 words, but if your story works longer or shorter, do that. Don't let a word count get in the way of a good story.&lt;/li&gt;
&lt;li&gt;Competition only open to African speculative fiction.&lt;/li&gt;
&lt;li&gt;Multiple submissions are fine, but you can only win for one.&lt;/li&gt;
&lt;li&gt;Original is preferred but – if you have rights to republish – a second publication is ok. This is especially welcomed if you've self-published elsewhere.&lt;/li&gt;
&lt;li&gt;Abide by &lt;a href="https://qwyre.com/get-streaming" rel="noopener noreferrer"&gt;Qwyre's rules&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most fiction competitions end there, with the prize. But Qwyre can offer everyone - including those who aren't amongst the ten - the opportunity to earn direct from readers from the moment they're published.&lt;/p&gt;

&lt;p&gt;Web monetized work earns about 36 cents per hour, or 0.6 cents per minute. For every minute someone reads your work on Qwyre, you get paid.&lt;/p&gt;

&lt;p&gt;To put this in perspective, streaming music services pay about 0.003 to 0.12 cents per 5-minute song. A 5,000 word short-story would take about 12 minutes to read and earn 7.2 cents. You need to be read 700 times to earn $50.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qwyre authors share in this income directly and in real time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you've self-published on Amazon, you know how their approach works. They take all the money and, some interminable time later, they pay you your share. Web monetization means you get paid in real time. Instead of us taking the money, calculating shares, and sending to different people, we divert payments to parties as people read.&lt;/p&gt;

&lt;p&gt;If you agree to pay an editor 15%, and 85% for yourself, then there is a three-way split: 12:73:15 (this ratio takes into account Qwyre's 15% share). Web monetization is per-second payments, and we push payments to each party in this agreed ratio. Everyone gets paid right away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which means - just like other publishing platforms - you need to create an account so you can be paid.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's how all this works ...&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing your work on Qwyre.com
&lt;/h2&gt;

&lt;p&gt;First things first. &lt;em&gt;Open your veins and bleed.&lt;/em&gt; Write something. Once you have something written, proceed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prepare your work for publication
&lt;/h3&gt;

&lt;p&gt;There are plenty of ways independent authors can self-publish. That lack of any friction between the author and the reader is one reason so much is produced, and with such low quality. Commercially-published work benefits from the simple barrier of being edited. Just &lt;em&gt;that&lt;/em&gt; surfaces poor prose and obscure, or irrational, storytelling.&lt;/p&gt;

&lt;p&gt;Qwyre is about collaborative publication, so here's the workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Start at your new creator's workshop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/Tn7LTatCfCAgcBQ0ux-9KNU2VnQdgSaq6PW_FyXqEJ4/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2k5dTB2OGRp/bXRmaTUycHExZ3Br/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/Tn7LTatCfCAgcBQ0ux-9KNU2VnQdgSaq6PW_FyXqEJ4/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2k5dTB2OGRp/bXRmaTUycHExZ3Br/LmpwZw" alt="Creator's workshop page" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you'll find links to the &lt;em&gt;docx&lt;/em&gt; convertor, as well as panels to review your works in progress, and offers for editors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;a href="https://qwyre.com/convert/description" rel="noopener noreferrer"&gt;Convert your Word document &lt;em&gt;docx&lt;/em&gt; to an &lt;em&gt;epub&lt;/em&gt;&lt;/a&gt;&lt;/strong&gt;, in a three step process where you'll describe your work (title, author, publishing date, keywords, that sort of thing ...), upload the document and cover, and then build the standards-compliant epub. This allows Qwyre to stream your work to readers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/wmo6cUnJ66PvXqzLh9mfIx5_dlg-AtyosCVgcv9UFsE/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2lpbGExZGx2/YWkwaWtjM2x0bXAw/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/wmo6cUnJ66PvXqzLh9mfIx5_dlg-AtyosCVgcv9UFsE/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2lpbGExZGx2/YWkwaWtjM2x0bXAw/LmpwZw" alt="Describe your work so we know what it is" width="562" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Import your &lt;em&gt;epub&lt;/em&gt;&lt;/strong&gt;, either during the &lt;em&gt;docx&lt;/em&gt; conversion process or, if you've produced your &lt;em&gt;epub&lt;/em&gt; in some other way, direct into the platform:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/5uU9YgSLYsgReF-pr0_9RtSCR4tkAEFsZwCTSeyjb6o/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2RqbXQ1NTg4/bnhpOWl0MTZjd25x/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/5uU9YgSLYsgReF-pr0_9RtSCR4tkAEFsZwCTSeyjb6o/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2RqbXQ1NTg4/bnhpOWl0MTZjd25x/LmpwZw" alt="Import an epub, either from a link or a local file" width="657" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is intended to be as easy as possible. If you've got an external link or a local file, either can be imported.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Review your work&lt;/strong&gt; and check that metadata (your descriptions and keywords), the contents and sample text are presented as expected:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/W1ktSHaePxa2k9ALfl0eoDepGa4IWJlVoCFNVaTi9fA/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzk5NGo4MWpj/bGh0dWdsbWRwaHRw/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/W1ktSHaePxa2k9ALfl0eoDepGa4IWJlVoCFNVaTi9fA/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzk5NGo4MWpj/bGh0dWdsbWRwaHRw/LmpwZw" alt="Review panel drawn direct from the epub" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The metadata and text are derived entirely from the &lt;em&gt;epub&lt;/em&gt; you uploaded. If anything is incorrect it may be because of non-conformance to the epub standard, or because the text is wrong in the source. Correct that, upload a new epub, and ensure you're happy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Prepare a contract&lt;/strong&gt; with your revenue share between yourself and prospective editors. This is what will be shared between the author and editor during monetization.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/7Zo_yk-qpQDbS2Y9l8jI9CePygOB8-TZg4mYslQyyAk/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzlqbHAyeXI5/N2VhdnN2amhwenE1/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/7Zo_yk-qpQDbS2Y9l8jI9CePygOB8-TZg4mYslQyyAk/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzlqbHAyeXI5/N2VhdnN2amhwenE1/LmpwZw" alt="Attempting to claim ownership of 'The Magician' by W. Somerset Maugham" width="712" height="748"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anyone can be an editor&lt;/strong&gt; and I encourage writers to offer to edit other writers work. Not only does this help you improve as a writer, but you also have the opportunity to share in the revenue of a greater diversity of work. &lt;strong&gt;Editors are paid too!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Offline collaboration for publication&lt;/strong&gt; is an important part of the creative process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/wHVC-eo2jRv5fY4YAHhJC3EtGb8TLvOG_NMVhOuT1qY/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2V6bnJqam02/cmY5MDZhOGY1eWdh/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/wHVC-eo2jRv5fY4YAHhJC3EtGb8TLvOG_NMVhOuT1qY/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2V6bnJqam02/cmY5MDZhOGY1eWdh/LmpwZw" alt="The contract and publication review panel" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The review and collaboration process is initiated via email. Sharing the editor's email address with the author only when offered, and then getting out of their way once they finalise their contract. Qwyre is not going to own the editorial process and the parties to the creative work should edit and review as they prefer.&lt;/p&gt;

&lt;p&gt;Only once both parties have agreed the contract, and agreed the work is ready for publication, can the creator release the work onto the platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Set up your payment pointer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can't use your credit card and need a dedicated payment pointer to start receiving streaming payments. This is straightforward and well-described here &lt;a href="https://webmonetization.org/docs/ilp-wallets" rel="noopener noreferrer"&gt;Digital Wallet and Payment Pointers&lt;/a&gt; for each of the current wallet providers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://webmonetization.org/docs/uphold#find-your-payment-pointer" rel="noopener noreferrer"&gt;Uphold&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webmonetization.org/docs/gatehub" rel="noopener noreferrer"&gt;Gatehub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have your pointer, simply save it to your personal profile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're ready to be read!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Readers can search for published works and start streaming&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/mA0ARajWjYHuac-jTP8e1JVbr4mazrj2OPjio11lt6I/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL3Y3czJsamVq/MjBkdmVwdWY3ajRh/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/mA0ARajWjYHuac-jTP8e1JVbr4mazrj2OPjio11lt6I/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL3Y3czJsamVq/MjBkdmVwdWY3ajRh/LmpwZw" alt="Mobile-friendly epub streaming" width="432" height="880"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is as little friction between reader and reading as possible. You don't need to login to start reading a work. You don't even need to pay for the initial pages. The app works for free, giving a reader the opportunity to anonymously try out the app, and your creative work.&lt;/p&gt;

&lt;h2&gt;
  
  
  A call to readers and writers
&lt;/h2&gt;

&lt;p&gt;So ... there it is. I created Qwyre in part because of my frustration with app-based self-publishing services and my desire to read more African speculative fiction.&lt;/p&gt;

&lt;p&gt;I you can write, &lt;strong&gt;please write&lt;/strong&gt;. If you can edit, &lt;strong&gt;please edit&lt;/strong&gt;. And if you can't do either, &lt;strong&gt;help me get this message out!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you have any questions or concerns &lt;a href="https://qwyre.com/contact-us" rel="noopener noreferrer"&gt;email me&lt;/a&gt;, or drop a comment in the chat. I would love your help in spreading the word and helping to draw out writers to Qwyre and web monetization. And I hope, soon, to be reading your work.&lt;/p&gt;

</description>
      <category>grantreports</category>
      <category>publishing</category>
      <category>scifi</category>
      <category>competitions</category>
    </item>
    <item>
      <title>Qwyre.com — Converting a digital work into a monetized stream [Grant Report #1]</title>
      <dc:creator>Gavin Chait</dc:creator>
      <pubDate>Mon, 03 Jan 2022 18:04:55 +0000</pubDate>
      <link>https://community.interledger.org/qwyre/qwyrecom-converting-a-digital-work-into-a-monetized-stream-grant-report-1-51gh</link>
      <guid>https://community.interledger.org/qwyre/qwyrecom-converting-a-digital-work-into-a-monetized-stream-grant-report-1-51gh</guid>
      <description>&lt;p&gt;Projects are like London buses. You wait for hours, standing alone in the pouring rain at midnight freezing your important bits and wondering if you'll ever be warm again, when two come along together.&lt;/p&gt;

&lt;p&gt;And so I found myself, six months ago, navigating two wonderful projects while hoping that I'd pull it off. &lt;/p&gt;

&lt;p&gt;Gentle reader. Working 14-hour days, seven days a week, for six months, is not a healthy pursuit. Hence the break in these monthly reports. My apologies. But the last month has been well spent with an &lt;a href="https://qwyre.com" rel="noopener noreferrer"&gt;interim release&lt;/a&gt; for you to play with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/Xqw5aG00VRUH_O1UVH8QZ0RzTKUDFoqFmPsrfrbEAhY/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL3R0dTlrZTN3/eXpsMW1rNnE4cmht/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/Xqw5aG00VRUH_O1UVH8QZ0RzTKUDFoqFmPsrfrbEAhY/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL3R0dTlrZTN3/eXpsMW1rNnE4cmht/LmpwZw" alt="Qwyre.com landing page and navigation bar" width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the most gratifying new features (for me, as a lazy person) is that you can paste a link to an epub and simply import direct into the app. And here a shout-out to &lt;a href="https://standardebooks.org/" rel="noopener noreferrer"&gt;Standard eBooks&lt;/a&gt; which are a wonderful community project reformatting public domain books into standards-compliant epubs.&lt;/p&gt;

&lt;p&gt;If you're a self-published author and want to link to the app direct and don't want your readers to have to faff about, generate a link yourself:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/ZlUMKSmyOazNHf_FNJDsyZ7bitfPz3bU3uGUx7YO8ac/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzR5MjV3amc0/NTZsaXF6cTI4Y3Zk/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/ZlUMKSmyOazNHf_FNJDsyZ7bitfPz3bU3uGUx7YO8ac/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzR5MjV3amc0/NTZsaXF6cTI4Y3Zk/LmpwZw" alt="Workshop external link generator" width="644" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Converting digital objects into digital streams
&lt;/h2&gt;

&lt;p&gt;Web Monetization is a per-second payment stream and it requires a per-second product. Some things are naturals for this, and they're usually live or where the digital service is tightly linked to a platform. Text isn't normally streamed, but music and movies can easily be downloaded as well.&lt;/p&gt;

&lt;p&gt;Streaming means deliberately breaking data into chunks and streaming those in a format that permits live, iterative, building of the digital object from these chunks while also serving the content in that dynamic object.&lt;/p&gt;

&lt;p&gt;For text, that means a stream of words. Except they also have to carry markup so they're formatted properly on arrival. Then the page needs to be updated without offending the reader. It's not trivial.&lt;/p&gt;

&lt;p&gt;For anyone else stuck with this, here is an exceptional &lt;a href="https://stackoverflow.com/questions/49330858/display-dynamic-html-content-like-an-epub-ebook-without-converting-html-to-epub/52194914#52194914" rel="noopener noreferrer"&gt;Stackoverflow answer&lt;/a&gt; that details not just the high-level strategy, but also provides a great foundation in code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Progress on objectives
&lt;/h2&gt;

&lt;p&gt;The status as of the previous updates (&lt;a href="https://community.webmonetization.org/qwyre/qwyre-com-a-framework-for-ethics-and-technical-development-in-fiction-publishing-3nln" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://community.webmonetization.org/qwyre/qwyre-com-what-does-a-minimum-loveable-product-even-look-like-22bm" rel="noopener noreferrer"&gt;2&lt;/a&gt;, &lt;a href="https://community.webmonetization.org/qwyre/qwyre-com-a-guide-for-integrating-coil-web-monetization-apis-for-creatives-doing-the-job-once-1f56" rel="noopener noreferrer"&gt;3&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Convert &lt;em&gt;docx&lt;/em&gt; into &lt;em&gt;epub&lt;/em&gt; using &lt;a href="https://github.com/whythawk/chapisha" rel="noopener noreferrer"&gt;Chapisha&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;Read &lt;em&gt;epub&lt;/em&gt; using &lt;a href="https://github.com/futurepress" rel="noopener noreferrer"&gt;Futurepress&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;Monetize using &lt;a href="https://coil.com" rel="noopener noreferrer"&gt;Coil&lt;/a&gt; (and please do read my &lt;a href="https://community.webmonetization.org/qwyre/qwyre-com-a-guide-for-integrating-coil-web-monetization-apis-for-creatives-doing-the-job-once-1f56" rel="noopener noreferrer"&gt;lengthy write-up&lt;/a&gt; on how to do this).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this report, these additional features are complete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Importing an *epub" for publication,&lt;/li&gt;
&lt;li&gt;Agreeing, and signing, a contract with an editor,&lt;/li&gt;
&lt;li&gt;Converting a work to a stream source,&lt;/li&gt;
&lt;li&gt;Streaming the work in response to web monetization.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Preparation of a work for publication
&lt;/h3&gt;

&lt;p&gt;There are plenty of ways independent authors can self-publish. That lack of any friction between the author and the reader is one reason so much is produced, and with such low quality. Commercially-published work benefits from the simple barrier of being edited. Just that surfaces poor prose and obscure, or irrational, storytelling.&lt;/p&gt;

&lt;p&gt;Qwyre is about collaborative publication, so here's the workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Start at your new creator's workshop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/Tn7LTatCfCAgcBQ0ux-9KNU2VnQdgSaq6PW_FyXqEJ4/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2k5dTB2OGRp/bXRmaTUycHExZ3Br/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/Tn7LTatCfCAgcBQ0ux-9KNU2VnQdgSaq6PW_FyXqEJ4/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2k5dTB2OGRp/bXRmaTUycHExZ3Br/LmpwZw" alt="Creator's workshop page" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you'll find links to the &lt;em&gt;docx&lt;/em&gt; convertor, as well as panels to review your works in progress, and offers for editors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Import your &lt;em&gt;epub&lt;/em&gt;&lt;/strong&gt;, either during the &lt;em&gt;docx&lt;/em&gt; conversion process or, if you've produced your &lt;em&gt;epub&lt;/em&gt; in some other way, direct into the platform:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/5uU9YgSLYsgReF-pr0_9RtSCR4tkAEFsZwCTSeyjb6o/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2RqbXQ1NTg4/bnhpOWl0MTZjd25x/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/5uU9YgSLYsgReF-pr0_9RtSCR4tkAEFsZwCTSeyjb6o/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2RqbXQ1NTg4/bnhpOWl0MTZjd25x/LmpwZw" alt="Import an epub, either from a link or a local file" width="657" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is intended to be as easy as possible. If you've got an external link or a local file, either can be imported.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Review the work&lt;/strong&gt; and check that metadata, the contents and sample text are presented as expected:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/W1ktSHaePxa2k9ALfl0eoDepGa4IWJlVoCFNVaTi9fA/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzk5NGo4MWpj/bGh0dWdsbWRwaHRw/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/W1ktSHaePxa2k9ALfl0eoDepGa4IWJlVoCFNVaTi9fA/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzk5NGo4MWpj/bGh0dWdsbWRwaHRw/LmpwZw" alt="Review panel drawn direct from the epub" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The metadata and text are derived entirely from the &lt;em&gt;epub&lt;/em&gt; you uploaded. If anything is incorrect it may be because of non-conformance to the epub standard, or because the text is wrong in the source. Correct that, upload a new epub, and ensure you're happy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Prepare a contract&lt;/strong&gt; with your revenue share between yourself and prospective editors. This is what will be shared between the author and editor during monetization.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/7Zo_yk-qpQDbS2Y9l8jI9CePygOB8-TZg4mYslQyyAk/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzlqbHAyeXI5/N2VhdnN2amhwenE1/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/7Zo_yk-qpQDbS2Y9l8jI9CePygOB8-TZg4mYslQyyAk/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzlqbHAyeXI5/N2VhdnN2amhwenE1/LmpwZw" alt="Attempting to claim ownership of 'The Magician' by W. Somerset Maugham" width="712" height="748"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's another reason to only permit publication after review by an editor. Here I'm attempting to claim authorship of &lt;a href="https://standardebooks.org/ebooks/w-somerset-maugham/the-magician" rel="noopener noreferrer"&gt;'The Magician' by W. Somerset Maugham&lt;/a&gt;. No, that should never be accepted. Any editor should catch that right away and refuse even to accept an offer to collaborate on the work in the first place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Offline collaboration for publication&lt;/strong&gt; is an important part of the creative process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/wHVC-eo2jRv5fY4YAHhJC3EtGb8TLvOG_NMVhOuT1qY/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2V6bnJqam02/cmY5MDZhOGY1eWdh/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/wHVC-eo2jRv5fY4YAHhJC3EtGb8TLvOG_NMVhOuT1qY/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2V6bnJqam02/cmY5MDZhOGY1eWdh/LmpwZw" alt="The contract and publication review panel" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The review and collaboration process is initiated via email. Sharing the editor's email address with the author only when offered, and then getting out of their way once they finalise their contract. Qwyre is not going to own the editorial process and the parties to the creative work should edit and review as they prefer.&lt;/p&gt;

&lt;p&gt;Only once both parties have agreed the contract, and agreed the work is ready for publication, can the creator release the work onto the platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Readers can search for published works and start streaming&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/mA0ARajWjYHuac-jTP8e1JVbr4mazrj2OPjio11lt6I/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL3Y3czJsamVq/MjBkdmVwdWY3ajRh/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/mA0ARajWjYHuac-jTP8e1JVbr4mazrj2OPjio11lt6I/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL3Y3czJsamVq/MjBkdmVwdWY3ajRh/LmpwZw" alt="Mobile-friendly epub streaming" width="432" height="880"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is as little friction between reader and reading as possible. You don't need to login to start reading a work. You don't even need to pay for the initial pages. The app works for free, giving a reader the opportunity to anonymously try out the app, and the creative work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Seamless call to monetization&lt;/strong&gt;, and a judgement call ...&lt;/p&gt;

&lt;p&gt;The sign-up process involves two steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a Qwyre.com account&lt;/li&gt;
&lt;li&gt;Creating a Coil.com subscription and linking that to Qwyre.com&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One reason was to physically seperate Qwyre.com from monetization. The app itself stores nothing about monetization out of the browser. There's no account to delete or concern about data probity. Sure, not forcing the user to create an account would simplify sign-up. That way we only have to worry about the reader signing up with Coil.&lt;/p&gt;

&lt;p&gt;However ... I need some way to keep track of where the reader has gotten to in streaming any work so that they are not double-charged for the same thing across multiple devices. That requires a login.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/B59CvKYx1Qnle1HxqtZI1d30aHOCwldm5c_L7hg3Fc0/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzE1ZnRkaHE2/ZjM0Z3lwMmtoeGpr/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/B59CvKYx1Qnle1HxqtZI1d30aHOCwldm5c_L7hg3Fc0/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzE1ZnRkaHE2/ZjM0Z3lwMmtoeGpr/LmpwZw" alt="Interstitial call to create an account and sign-up with Coil" width="641" height="713"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the reader has authenticated everything, they are still in complete control of the monetization process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/4kOCsClmiq90iD3X_KCjrxGdQhO8xxr86Lm-40HAeOU/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL211aTZ2d3dm/OHY3czY2czd6NGVj/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/4kOCsClmiq90iD3X_KCjrxGdQhO8xxr86Lm-40HAeOU/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL211aTZ2d3dm/OHY3czY2czd6NGVj/LmpwZw" alt="Interstitial for logged-in readers asking them to start streaming" width="435" height="881"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Development and monetization receipts ...
&lt;/h2&gt;

&lt;p&gt;This is harder than it needs to be. There are no development APIs, so you're working with live Coil accounts, and the largest challenge is proving that you are getting paid.&lt;/p&gt;

&lt;p&gt;Webmonetization offers a &lt;a href="https://webmonetization.org/docs/receipt-verifier" rel="noopener noreferrer"&gt;receipt verifier service&lt;/a&gt;. This is yet another third-party layer between your app and payment. Instead of embedding wallet pointers into your code, you now interpose a verifier endpoint: &lt;code&gt;$receipt-verifier.example/%24wallet.example%2Falice&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It's a complex requirement and immediately runs into CORS issues, meaning you'll need to implement this all yourself creating additional opportunities for broken/dangerous code.&lt;/p&gt;

&lt;p&gt;Some recommendations I'd like to see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We need some developer endpoints. I want to test out what happens when payments fail in various scenarios. This is hard to do with a live account.&lt;/li&gt;
&lt;li&gt;The Web Monetization scripts returns a series of &lt;a href="https://webmonetization.org/docs/api" rel="noopener noreferrer"&gt;events&lt;/a&gt;. It would be tremendous if this included a receipt by default. If I want to implement my own receipts verifier, fine, but not having it here creates sequencing issues. Receipts need to be verified continuously for continuous payments. The more endpoints to pole, the more chance for glitches and delays.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;The last outstanding feature is the search engine. Once that is done, I'll release the final version. I also intend to run a demo with the &lt;a href="https://www.africansfs.com/" rel="noopener noreferrer"&gt;African Speculative Fiction Society&lt;/a&gt; members and get some feedback on the work to date. The 2021 Nommo Awards were just announced so go find some African scifi to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  What community support would benefit your project?
&lt;/h2&gt;

&lt;p&gt;I'd appreciate thoughts and feedback on the publication workflow, as well as on the interim release at &lt;a href="https://qwyre.com" rel="noopener noreferrer"&gt;Qwyre.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>grantreports</category>
      <category>publishing</category>
      <category>ethics</category>
      <category>scifi</category>
    </item>
    <item>
      <title>Qwyre.com - A guide for integrating Coil &amp; Web Monetization APIs for creatives doing the job once</title>
      <dc:creator>Gavin Chait</dc:creator>
      <pubDate>Fri, 03 Sep 2021 17:46:05 +0000</pubDate>
      <link>https://community.interledger.org/qwyre/qwyre-com-a-guide-for-integrating-coil-web-monetization-apis-for-creatives-doing-the-job-once-1f56</link>
      <guid>https://community.interledger.org/qwyre/qwyre-com-a-guide-for-integrating-coil-web-monetization-apis-for-creatives-doing-the-job-once-1f56</guid>
      <description>&lt;p&gt;There are two principle types of developer: journeymen and creatives. A journeyman gains experience through repetition. Overly-technical or complex documentation isn't a problem since the journeyman uses it more like landmarks. The creative coder is developing software as a means to express their idea. They know where they want to go but are covering the ground once.&lt;/p&gt;

&lt;p&gt;This guide for integrating Coil &amp;amp; Web Monetization APIs is written for creatives.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why implement the API?&lt;/strong&gt; It is your responsibility - as creative - to ensure that your prospective subscribers and supporters can easily, and quickly, i) understand what it is that you are offering, and ii) pay for it in a way that is easy and doesn't scare them. You cannot rely on them to install a browser plugin for a technology they may never have heard of. &lt;/p&gt;

&lt;p&gt;There is a secondary reason ... the API offers you more flexibility and creativity in what you can offer. It is more expressive.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Notes to the guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Approach
&lt;/h3&gt;

&lt;p&gt;The first step is to have a &lt;code&gt;high-level&lt;/code&gt; understanding of exactly what needs to happen to authenticate a subscriber and authorise web monetization. This may feel like repetition, but that means we go through this twice. First to get a broad understanding, then into the &lt;code&gt;technical detail&lt;/code&gt; and &lt;strong&gt;gotchas&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I recommend you follow along in the reference documentation (see &lt;strong&gt;References&lt;/strong&gt; at the end), &lt;strong&gt;and&lt;/strong&gt; this guide. This guide compliments the documentation and is &lt;strong&gt;not&lt;/strong&gt; a substitute for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Development stack
&lt;/h3&gt;

&lt;p&gt;I make the assumption you know how to code and that you know enough about best-practice in app development that I won't need to include everything. I provide most code required for implementation, but the tech stack is very specific:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nuxtjs.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;Nuxt.js&lt;/strong&gt;&lt;/a&gt; - A Vue.js framework, based on Node.js, for the Progressive Web App &lt;code&gt;frontend&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fastapi.tiangolo.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;FastAPI&lt;/strong&gt;&lt;/a&gt; - A Python framework for building APIs, for the server &lt;code&gt;backend&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The coding languages are &lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://www.python.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/a&gt; with annotations (specifically &lt;a href="https://pydantic-docs.helpmanual.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Pydantic&lt;/strong&gt;&lt;/a&gt;). These annotations ensure code consistency and reduce the number of gotchas.&lt;/p&gt;

&lt;p&gt;The complete development stack is available as a Docker Compose base project generator:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/whythawk" rel="noopener noreferrer"&gt;
        whythawk
      &lt;/a&gt; / &lt;a href="https://github.com/whythawk/full-stack-fastapi-postgresql" rel="noopener noreferrer"&gt;
        full-stack-fastapi-postgresql
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Full stack, modern web application generator. Using FastAPI, PostgreSQL as database, Nuxt3, Docker, automatic HTTPS and more.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Gotchas
&lt;/h3&gt;

&lt;p&gt;There are a &lt;em&gt;LOT&lt;/em&gt; of gotchas. These will be presented as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; a gotcha is an easy-to-miss detail, which - once overlooked - will cause you hours / days of lost time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Consider this a first draft, and please do respond in the comments below if anything is unclear, or you see a better / more efficient way of doing things.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's get started.&lt;/p&gt;




&lt;h2&gt;
  
  
  Web Monetization is about &lt;code&gt;authentication&lt;/code&gt; and &lt;code&gt;authorisation&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Your app is in the middle between your subscribers and Coil as monetization provider. Your objectives are &lt;code&gt;authentication&lt;/code&gt; - prove who you are - and &lt;code&gt;authorisation&lt;/code&gt; - prove you have authority to perform an action.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your app needs to perform each of these tasks with each party.&lt;/li&gt;
&lt;li&gt;You need to tool up your app so that it has the necessary credentials and scripts to perform these tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are four separate components you'll need to build:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/zr-U-vcE4gDKX3jZFbvFyhMSmF7uWFVGOISLHVInZTU/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2VwcWxvN3Vh/c3luNDE5M2NhNmY0/LnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/zr-U-vcE4gDKX3jZFbvFyhMSmF7uWFVGOISLHVInZTU/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2VwcWxvN3Vh/c3luNDE5M2NhNmY0/LnBuZw" alt="monetization-guide-flowchart" width="721" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authenticate your app with Coil:&lt;/strong&gt; you'll do this once for each app - a minimum of twice, for your development and production environments, and is largely a manual process with application via email and an online form,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embed the OAuth Web Monetization (OWM) script in your app:&lt;/strong&gt; again, once, as you develop your app,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your subscriber authenticates your rights to their monetisation account:&lt;/strong&gt; for each new subscriber,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your subscriber authorises your right to monetize your content:&lt;/strong&gt; for each subscriber, every time they permit monetization, and refreshed every 30 minutes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how your subscriber will experience the process:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. They visit your app&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/az1UfYps9N077bO-CoWWn7yj--tg5VFkUNnmYJdFCjk/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2p5bHU5cGE5/aHN1ZDNkcmV6NjEx/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/az1UfYps9N077bO-CoWWn7yj--tg5VFkUNnmYJdFCjk/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2p5bHU5cGE5/aHN1ZDNkcmV6NjEx/LmpwZw" alt="monetization-app-landing-page" width="512" height="596"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. They decide to authorise your app to receive payments and click on a link you generate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/jD-92sxWSAAO8UVeChIS6zCNi4yGW2FDwlUAjsXx1jE/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2I1MnJjZjBx/MjdwdWRyOXYyd3Nr/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/jD-92sxWSAAO8UVeChIS6zCNi4yGW2FDwlUAjsXx1jE/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2I1MnJjZjBx/MjdwdWRyOXYyd3Nr/LmpwZw" alt="monetization-app-sign-in-with-coil" width="509" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. They are referred to Coil where they must approve authentication.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/4EeflDjcfeVrt7qhFpkG4fWektn5Lf4F9x3JRlZu0Sk/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzZkOWw5aDI0/OGl2aXppZGM3dGc4/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/4EeflDjcfeVrt7qhFpkG4fWektn5Lf4F9x3JRlZu0Sk/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzZkOWw5aDI0/OGl2aXppZGM3dGc4/LmpwZw" alt="monetization-app-subscriber-coil-auth" width="525" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Coil refers the subscriber back to a specific receiving page where your app needs to process the token it receives and authenticate.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/oDLnGd5GxA0keLZSJ058H6olwu26GeK6AesIZZGpVzk/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2t0NmwyOGNx/a2Q4d2I1dGZvZDRm/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/oDLnGd5GxA0keLZSJ058H6olwu26GeK6AesIZZGpVzk/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2t0NmwyOGNx/a2Q4d2I1dGZvZDRm/LmpwZw" alt="monetization-app-authenticating" width="509" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Your app updates the subscriber and invites them to begin streaming.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/goD61MeDAFDkN2bn58k4qnuHWVGPye7zse7yzNI_XpI/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2VkOHA4aDBl/MndnYjZudzlkcnZv/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/goD61MeDAFDkN2bn58k4qnuHWVGPye7zse7yzNI_XpI/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2VkOHA4aDBl/MndnYjZudzlkcnZv/LmpwZw" alt="monetization-app-ready-to-stream" width="508" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only one of these steps requires your subscriber to leave your app and make an active authentication decision. Thereafter, how you decide to start and stop monetization is up to you.&lt;/p&gt;

&lt;p&gt;Now for the technical details.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Authenticate your app with Coil
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/QA1sG7WNpFSVPZkzhQNX6Ebc05wy5GXIXfVbZ6SM7GE/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL3hhbHp5MWhy/dGE2d3ljYndnMXJs/LnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/QA1sG7WNpFSVPZkzhQNX6Ebc05wy5GXIXfVbZ6SM7GE/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL3hhbHp5MWhy/dGE2d3ljYndnMXJs/LnBuZw" alt="image" width="800" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;At the end of this process you will have:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;client_id&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;client_secret&lt;/code&gt; converted to base64.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a strictly manual process and takes some time. Do it earlier rather than later. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Follow Coil's &lt;a href="https://help.coil.com/docs/dev/oauth-api" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; to register your developer account, and email &lt;code&gt;devs@coil.com&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It is normal to wait a week or more for them to respond, when they do, you will be able to register apps and request &lt;code&gt;authentication tokens&lt;/code&gt;,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://coil.com/oauth_register" rel="noopener noreferrer"&gt;https://coil.com/oauth_register&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client app name: qwyre-local&lt;/li&gt;
&lt;li&gt;Redirect URIs (comma-separated): &lt;a href="https://localhost:3000" rel="noopener noreferrer"&gt;https://localhost:3000&lt;/a&gt;, &lt;a href="https://localhost:3000/support-us" rel="noopener noreferrer"&gt;https://localhost:3000/support-us&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Logo URI: &lt;a href="https://localhost:3000/qwyre-logo.svg" rel="noopener noreferrer"&gt;https://localhost:3000/qwyre-logo.svg&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Make a note of the app registration details as they are not listed anywhere afterwards, and you must have them exact for the next step.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; before you decide on the &lt;code&gt;redirect URI&lt;/code&gt; pause to think. This URI will become the most important page in managing this process, and it may not work for you if that page is &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you receive your app authentication token from Coil, you will need to register with &lt;a href="https://openid.net/connect/" rel="noopener noreferrer"&gt;Coil's Open ID Connect (OIDC)&lt;/a&gt; provider to exchange the token for a client ID and client secret. You can do this from a &lt;code&gt;bash&lt;/code&gt; command line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://help.coil.com/docs/dev/post-oauth-reg#register-your-app-with-the-oidc-provider" rel="noopener noreferrer"&gt;Coil's documentation&lt;/a&gt; is relatively straightforward. Dig out the app registration terms you used above:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;request&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST https://coil.com/oauth/reg \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer Pb8w98v18ikkZyy26nxXK5OKDDsN6kfEJVmQ2id9tbC' \
  -d \
  '{
    "redirect_uris":["http://localhost:3000","http://localhost:3000/support-us"],
    "client_name": "qwyre-local",
    "tos_uri": "https://localhost:3000/privacy/",
    "policy_uri": "https://localhost:3000/privacy/",
    "logo_uri": "http://localhost:3000/qwyre-logo.svg"
  }'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; &lt;code&gt;tos_uri&lt;/code&gt; and &lt;code&gt;policy_uri&lt;/code&gt; must be &lt;code&gt;https&lt;/code&gt; otherwise the request will fail. It doesn't matter whether your development environment is not certified (although you can issue yourself a self-signed certificate if you like). The end-points are not tested, only the form of the URI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;response&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "client_id": "314ac134-fc3c-4d28-bf43-ccb75a2f9fb2",
  "client_secret": "uVE2t7y1QvyM78PlBA3aQAUh6syXVw7P2XBr4QDsS2yrkETR6al9YFpH4NDloXh5",
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is abbreviated, and is the only time you'll get this, so save these details somewhere safe. These are the only two keys you need (for now): &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; Not really a gotcha, since it's clearly flagged in the docs, however, before you can use your &lt;code&gt;client_secret&lt;/code&gt;, you need to make the string web-safe since you will be sending this in your first GET request. That requires converting it to base 64. In Python, that's pretty easy:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import base64
base64.b64encode(bytes(client_secret, 'utf-8'))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Then use that string as your &lt;code&gt;client_secret&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2. Embed the OAuth Web Monetization (OWM) script in your app
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/WdSozIOLmif5tmmdc4FG5hAH6nF6h5-3qUmmh94K1Ds/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzYyYnl3bjl3/aGMzNW40YmNqdGhw/LnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/WdSozIOLmif5tmmdc4FG5hAH6nF6h5-3qUmmh94K1Ds/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzYyYnl3bjl3/aGMzNW40YmNqdGhw/LnBuZw" alt="monetization-2-embed-owm-script" width="701" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;At the end of this process you will have:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Embedded Coil's OWM script in your app.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://help.coil.com/docs/dev/oauth-web-monetization-script" rel="noopener noreferrer"&gt;Coil describes this&lt;/a&gt; as a relatively straightforward process. It is not.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; the Coil OWM script does not play well with Nuxt.js and, I'm assuming, any Node-based JavaScript/TypeScript framework that performs server-side-rendering and hydration. If you don't know what this means, just be aware that this process is the source of many gotchas, not just when implementing web monetization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Coil recommends you create a base &lt;code&gt;index.html&lt;/code&gt; file as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta name="monetization" content="$wallet.example.com/~alice"&amp;gt;
    &amp;lt;script&amp;gt;
      if (document.monetization) {
        document.monetizationExtensionInstalled = true
      } else {
        document.monetization = document.createElement('div')
        document.monetization.state = 'stopped'
      }
    &amp;lt;/script&amp;gt;
    &amp;lt;script src="https://cdn.coil.com/coil-oauth-wm.v7.beta.js" defer&amp;gt;
    &amp;lt;/script&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;p&amp;gt;Testing web monetization via coil polyfill using btp token.&amp;lt;/p&amp;gt;
    &amp;lt;script&amp;gt;
      document.coilMonetizationPolyfill.init({ btpToken: 'e0y1...' })
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You'll notice three important components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;meta&lt;/code&gt; tag with the destination wallet for streaming payments,&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;script&lt;/code&gt; tag with &lt;code&gt;defer&lt;/code&gt; import of the OWM script,&lt;/li&gt;
&lt;li&gt;And an &lt;code&gt;if else&lt;/code&gt; statement setting up the &lt;code&gt;div&lt;/code&gt; for interacting with &lt;code&gt;document.monetization&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Something that I spent quite a bit of time frustrated by is that I couldn't interact with the OWM script in any way &lt;strong&gt;until after&lt;/strong&gt; it had been initialised by creating that seemingly innocuous &lt;code&gt;div&lt;/code&gt;. This has implications for the way Node interacts with the script.&lt;/p&gt;

&lt;p&gt;The recommended way to include external / third-party scripts in Node is via a &lt;code&gt;config.js&lt;/code&gt; file. For example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Global page headers: https://go.nuxtjs.dev/config-head
// https://vue-meta.nuxtjs.org/api/#script
// https://javascript.info/script-async-defer
head: {
    title: "Qwyre",
    meta: [
      { charset: "utf-8" },
      { name: "viewport", content: "width=device-width, initial-scale=1" },
      { name: "monetization", content: process.env.COIL_PAYMENT_POINTER },
      { hid: "description", name: "description", content: "" },
      { src: "https://cdn.coil.com/coil-oauth-wm.v7.beta.js", defer: true },
    ],
    link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }],
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then you would initialise the script in your &lt;code&gt;layout&lt;/code&gt; (base html templates to structure any pages). &lt;strong&gt;This does not work.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At this time, you need to bypass Node/Nuxt.js and initialise your &lt;code&gt;app.html&lt;/code&gt; (ordinarily not customised, and left to the framework to manage):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;!DOCTYPE html&amp;gt;
    &amp;lt;html {{ HTML_ATTRS }}&amp;gt;
      &amp;lt;head {{ HEAD_ATTRS }}&amp;gt;
        {{ HEAD }}
        &amp;lt;script&amp;gt;
            if (document.monetization) {
              document.monetizationExtensionInstalled = true
            } else {
              document.monetization = document.createElement('div')
              document.monetization.state = 'stopped'
            }
          &amp;lt;/script&amp;gt;
          &amp;lt;script src="https://cdn.coil.com/coil-oauth-wm.v7.beta.js" defer&amp;gt;&amp;lt;/script&amp;gt;
      &amp;lt;/head&amp;gt;
      &amp;lt;body {{ BODY_ATTRS }}&amp;gt;
        {{ APP }}
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even then, you will get some flaky responses. I am unclear as to whether this is normal, or related to the compromises.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Your subscriber authenticates your rights to their monetisation account
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/jvcCV8GelkqlDGGmQ-u6ghu3VZwDprSVCDAlRC8s0QI/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2ZlcDhrdm9l/dTFubGRrdG44MzM0/LnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/jvcCV8GelkqlDGGmQ-u6ghu3VZwDprSVCDAlRC8s0QI/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL2ZlcDhrdm9l/dTFubGRrdG44MzM0/LnBuZw" alt="monetization-3-subscriber-auth" width="701" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;At the end of this process you will have:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up TypeScript interfaces for &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;responses&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;Set up all API request code,&lt;/li&gt;
&lt;li&gt;Set up a proxy for queries to Coil,&lt;/li&gt;
&lt;li&gt;Store the &lt;code&gt;refresh_token&lt;/code&gt; as a cookie, and &lt;code&gt;access_token&lt;/code&gt; and &lt;code&gt;sub&lt;/code&gt; in the &lt;code&gt;store&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;[Optional] Request your subscriber's personal information.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maybe go get some coffee. This is going to get very technical, very fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up your TypeScript interfaces
&lt;/h3&gt;

&lt;p&gt;It is very easy to get lost amongst the keys and their requirements. TypeScript interfaces permit us to define what data we need to &lt;code&gt;request&lt;/code&gt;, and what we expect in &lt;code&gt;response&lt;/code&gt;. These are &lt;a href="https://help.coil.com/docs/dev/oauth-api" rel="noopener noreferrer"&gt;documented&lt;/a&gt; over several pages, but we'll pool them all in one, then use them throughout what follows. It will make things easier.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;./interfaces/coil.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    /* eslint-disable camelcase */

    export interface ICoilUserResourceRequest {
      response_type: string
      scope: string
      client_id: string
      state: string
      redirect_uri: string
      prompt: string
    }

    export interface ICoilUserResourceResponse {
      code: string
      state: string
      scope: string
    }

    export interface ICoilUserTokenRequest {
      code: string
      grant_type: string
      redirect_uri: string
    }

    export interface ICoilUserTokenResponse {
      access_token: string
      expires_in: number
      id_token: string
      refresh_token: string
      scope: string
      token_type: string
    }

    export interface ICoilUserTokenRefreshRequest {
      refresh_token: string
      grant_type: string
      scope: string
    }

    export interface ICoilUserTokenRevokeRequest {
      token: string
    }

    export interface ICoilUserInfo {
      email?: string
      sub: string
    }

    export interface ICoilUserBTP {
      btpToken: string
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That should be fairly readable. Interfaces for &lt;code&gt;request&lt;/code&gt; are followed by a counterparty &lt;code&gt;response&lt;/code&gt;. We make the request, we receive the response. It should all validate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up all API request code
&lt;/h3&gt;

&lt;p&gt;We'll go through this step-by-step ... I'm using &lt;a href="https://axios-http.com/docs/intro" rel="noopener noreferrer"&gt;Axios&lt;/a&gt; to make the requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Generate and present the referral URI to your subscriber&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; While Coil states that you can refer the subscriber to either of a &lt;code&gt;signup&lt;/code&gt; or &lt;code&gt;login&lt;/code&gt; page, the reality is only &lt;code&gt;login&lt;/code&gt; works. Note this for the &lt;code&gt;prompt&lt;/code&gt; key in the API.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Nuxt.js, create &lt;code&gt;./api/coil.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import axios from "axios"
import {
  ICoilUserResourceRequest,
  ICoilUserTokenRequest,
  ICoilUserTokenResponse,
  ICoilUserTokenRefreshRequest,
  ICoilUserTokenRevokeRequest,
  ICoilUserInfo,
  ICoilUserBTP,
} from "@/interfaces"

export const coil = {

    getUserResource(coilState: string): string {
        // https://help.coil.com/docs/dev/get-oauth-auth#request-parameters
        // NOTE: currently the 'signup' prompt doesn't work
        // https://www.valentinog.com/blog/url/
        const getResourceURL = new URL(`${process.env.coilUrl}/oauth/auth`)
        const data: ICoilUserResourceRequest = {
          response_type: "code",
          scope: "simple_wm openid",
          client_id: (process.env.coilID as unknown) as string,
          state: coilState,
          redirect_uri: `${process.env.baseUrl}/support-us`,
          prompt: "login",
        }
        for (const [key, value] of Object.entries(data)) {
          getResourceURL.searchParams.append(key, value)
        }
        return getResourceURL.href
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; &lt;code&gt;coilState&lt;/code&gt; is a really important token you will use to ensure you can trust the response from Coil (i.e. no spoofing of your website by sending random scripts to your processing page). Effectively, you generate a random string, send that to Coil along with your request, then Coil sends it back. It is &lt;strong&gt;YOUR&lt;/strong&gt; responsibility to validate the &lt;code&gt;state&lt;/code&gt; you receive as being identical to the one you sent. I generate these as UUIDs:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In &lt;code&gt;frontend&lt;/code&gt; create &lt;code&gt;./utilities/keys.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function generateUUID(): string {
  // Reference: https://stackoverflow.com/a/2117523/709884
  // And: https://stackoverflow.com/a/61011303/295606
  return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (s) =&amp;gt; {
    const c = Number.parseInt(s, 10)
    return (
      c ^
      (crypto.getRandomValues(new Uint8Array(1))[0] &amp;amp; (15 &amp;gt;&amp;gt; (c / 4)))
    ).toString(16)
  })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You will call and use the generated URI in your &lt;code&gt;redirect_uri&lt;/code&gt; page script. &lt;/p&gt;

&lt;p&gt;In &lt;code&gt;frontend&lt;/code&gt;, redirect processing page &lt;code&gt;./pages/support-us.vue&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;this.coilRequestURI = coil.getUserResource(this.coilState)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And then use this to generate the "Sign in with Coil" button:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a class="mt-9" :href="coilRequestURI"
  &amp;gt;&amp;lt;SvgIcon terms="w-56 h-10" icon="svgCoilWhite"
/&amp;gt;&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; you defined which page you want Coil to return the subscriber's response as &lt;code&gt;redirect_uri&lt;/code&gt;. It is this page where all the following processing will take place. Mine is &lt;code&gt;./pages/support-us.vue&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; You can't make requests from the &lt;code&gt;frontend&lt;/code&gt;. You run straight into &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;Cross-Origin Resource Sharing (CORS)&lt;/a&gt; errors where the requests and / or responses are blocked. You need to proxy your requests. This is especially painful if you were hoping for a Single-Page Application, or a static app.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fortunately, I'm using FastAPI on the &lt;code&gt;backend&lt;/code&gt; so it's relatively straightforward to set it up to proxy requests. We'll do that in the next section, so for here just note that the URIs for &lt;code&gt;axios.post&lt;/code&gt; and &lt;code&gt;axios.get&lt;/code&gt; are being requested from the &lt;code&gt;backend&lt;/code&gt;, not Coil.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Receive and use response token to request subscriber authentication keys&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The coil redirect will return to your response page, along with &lt;code&gt;query&lt;/code&gt; terms in the path. It may look something like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;response&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://example.com/
?code=CU6LG36vKvVmUbF9QWFwj7F5zvY
&amp;amp;state=b5f1872f-9d32-5f31-819d-5a4daeab4ea9
&amp;amp;scope=simple_wm openid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;First, parse this to extract the parameters and then send this for processing. I'm using the Nuxt Store to manage browser state.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;frontend&lt;/code&gt;, in redirect processing page &lt;code&gt;./pages/support-us.vue&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (
  this.$route.query.code &amp;amp;&amp;amp;
  this.$route.query.state &amp;amp;&amp;amp;
  this.$route.query.scope
) {
  const payload: ICoilUserResourceResponse = {
    code: this.$route.query.code as string,
    state: this.$route.query.state as string,
    scope: this.$route.query.scope as string,
  }
  await this.getUserToken(payload)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The asyncronous await to &lt;code&gt;getUserToken&lt;/code&gt; populates the store and triggers the cascade of authentication requests we need. In &lt;code&gt;./store/monetization/actions.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async getUserToken({ commit, dispatch }, payload: ICoilUserResourceResponse) {
    if (getCoilState() !== payload.state) {
      await commit("setRequestError", true)
    } else {
      try {
        try {
          const response: AxiosResponse = await coil.getUserToken(payload.code)
          const data: ICoilUserTokenResponse = response.data
          await commit("setCoilToken", data.access_token)
          saveCoilRefresh(data.refresh_token)
        } catch (error) {
          await dispatch("checkCoilError", error)
        }
      } catch (error) {
        await dispatch("checkCoilError", error)
      }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This requires a request to the coil API, and then we save the authentication token to the store, and the refresh token as a persistent cookie in the subscriber's browser.&lt;/p&gt;

&lt;p&gt;This is the critical self-validation call to ensure that the response you receive is valid and from Coil:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (getCoilState() !== payload.state) ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here is the next api call to Coil to pass back the token you received and exchange it for your subscriber's authentication and refresh tokens:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await coil.getUserToken(payload.code)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Back to &lt;code&gt;./api/coil.ts&lt;/code&gt; and pop this into the &lt;code&gt;export&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async getUserToken(coilCode: string) {
    // https://help.coil.com/docs/dev/post-oauth-token/index.html#request-an-access-token-for-an-authenticated-user
    const data: ICoilUserTokenRequest = {
      code: coilCode,
      grant_type: "authorization_code",
      redirect_uri: `${process.env.baseUrl}/create`,
    }
    return await axios.post&amp;lt;ICoilUserTokenResponse&amp;gt;(
      `${process.env.apiUrl}/api/v1/coil/proxy/oauth/token`,
      data,
      requestHeaders()
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is a &lt;code&gt;post&lt;/code&gt; and you pass your app's authentication keys to Coil in the header:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requestHeaders()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That function is included in &lt;code&gt;./api/coil.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function requestHeaders() {
  const token = btoa(`${process.env.coilID}:${process.env.coilSecret}`)
  return {
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Authorization: `Basic ${token}`,
    },
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; Note again that you're escaping your two keys, &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt;, as base64. Here using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/btoa" rel="noopener noreferrer"&gt;btoa&lt;/a&gt;. I don't know if this was just me, but I found that I had to do this 'twice'. I first converted &lt;code&gt;client_secret&lt;/code&gt; to base64, then generated a new base64 string on the result &lt;code&gt;btoa(client_id:client_secrect_base64)&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Coil returns a response &lt;code&gt;json&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;response&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "access_token": "eyJhbGciOi...JSUzI1NfsQ",
  "expires_in": 3600,
  "id_token": "eyJhbGciOiJSUz...I1NiIsInR5",
  "refresh_token": "dzfKQUEFYXEZ2~WKq5t0atT36X~",
  "scope": "simple_wm", "openid",
  "token_type": "Bearer"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Store the &lt;code&gt;refresh_token&lt;/code&gt; as a cookie, and &lt;code&gt;access_token&lt;/code&gt; and &lt;code&gt;sub&lt;/code&gt; in the &lt;code&gt;store&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This &lt;code&gt;access_token&lt;/code&gt; authenticates you to request a BTP token which is what you need to begin streaming money. This token is only valid for an hour, and so I don't bother storing it in a persistent cookie, leaving it in the store. However, the &lt;code&gt;refresh_token&lt;/code&gt; is valid indefinitely and so you need to take care with it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ETHICS:&lt;/strong&gt; You may be tempted to store this &lt;code&gt;refresh_token&lt;/code&gt; in your database. &lt;strong&gt;DO NOT DO SO&lt;/strong&gt;. It doesn't belong to you. It belongs to your subscriber. Should they wish to revoke your use of that token, it should never be a request to your database. They should have the power to simply clear their browser cookies.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Request your subscriber's personal information
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Optional:&lt;/strong&gt; Depending on how your app works, you may want your subscriber's personal information (user id and/or email). This is a separate request. It is &lt;strong&gt;not&lt;/strong&gt; needed to authorise streaming.&lt;/p&gt;

&lt;p&gt;In the store &lt;code&gt;./store/monetization/actions.ts&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async getUserInfo({ commit, dispatch, state }) {
    try {
      const response: AxiosResponse = await coil.getUserInfo(state.coilToken)
      const data: ICoilUserInfo = response.data
      await commit("setCoilSub", data.sub)
    } catch (error) {
      await dispatch("checkCoilError", error)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Back in &lt;code&gt;./api/coil.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async getUserInfo(accessToken: string) {
    return await axios.get&amp;lt;ICoilUserInfo&amp;gt;(
      `${process.env.apiUrl}/api/v1/coil/proxy/user/info`,
      requestUserHeaders(accessToken)
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; you are using both a new API endpoint for this request, as well as a new header. &lt;code&gt;https://api.coil.com&lt;/code&gt;, and this &lt;code&gt;header&lt;/code&gt;:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function requestUserHeaders(accessToken: string) {
  return {
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Authorization: `Bearer ${accessToken}`,
    },
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Where &lt;code&gt;accessToken&lt;/code&gt; is the &lt;code&gt;access_token&lt;/code&gt; you received from &lt;code&gt;getUserToken&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In addition, your request to &lt;code&gt;getUserInfo&lt;/code&gt; is a &lt;code&gt;get&lt;/code&gt; request. That's a minor change here, but a big change on your proxy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;response&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "sub": "22028a7c-b720-7a3e-4387-6c9845f6201b",
  "email": "alice@coil.com"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Set up a &lt;code&gt;backend&lt;/code&gt; proxy for queries to Coil
&lt;/h3&gt;

&lt;p&gt;As described earlier, all of the API calls in the Nuxt &lt;code&gt;frontend&lt;/code&gt; are proxied to the FastAPI &lt;code&gt;backend&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The way this works is, your &lt;code&gt;frontend&lt;/code&gt; interacts with the subscriber and manages the monetization state. It then sends any queries it has direct to the server and immediately refers back any responses. It stores nothing and remembers nothing.&lt;/p&gt;

&lt;p&gt;Python code follows, using &lt;a href="https://www.python-httpx.org/" rel="noopener noreferrer"&gt;HTTPX&lt;/a&gt; as the http client.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ETHICS:&lt;/strong&gt; You'll see that, while your subscriber is anonymous (as are their payments), control over when to charge them and how to do so is not actually in their control. It's entirely in yours. That means you have to deliberately - from the beginning - make it easy to &lt;strong&gt;NOT ABUSE YOUR SUBSCRIBER&lt;/strong&gt;. The only way you can do that effectively is if you resolve never to store any auth keys anywhere near your backend, but leave them entirely in your subscriber's browser. This is the reason we only proxy at the backend and not manage the entire process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What follows is longer than it needs to be because of yet another gotcha.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; Coil's API has two inconsistencies. The first is that not all requests are &lt;code&gt;POST&lt;/code&gt;. There is one &lt;code&gt;GET&lt;/code&gt;. The second is that not all requests are to the same URI. It switches from &lt;code&gt;https://coil.com&lt;/code&gt; to &lt;code&gt;https://api.coil.com&lt;/code&gt;. You need to account for this in your proxy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Create &lt;code&gt;./app/api/api_v1/endpoints/coil.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    from typing import Any
    from fastapi import APIRouter, HTTPException, Request, Response
    import httpx

    from app.core.config import settings


    router = APIRouter()
    # https://coil.com
    coil_uri = settings.COIL_URI
    # https://api.coil.com
    coil_api_uri = settings.COIL_API_URI


    # FOR POST
    @router.post("/proxy/{path:path}")
    async def proxy_post_request(*, path: str, request: Request) -&amp;gt; Any:
        # https://www.starlette.io/requests/
        # https://www.python-httpx.org/quickstart/
        # https://github.com/tiangolo/fastapi/issues/1788#issuecomment-698698884
        # https://fastapi.tiangolo.com/tutorial/path-params/#__code_13
        try:
            URI = coil_uri
            if path.startswith("user/"):
                URI = coil_api_uri
            data = await request.json()
            headers = {
                "Content-Type": request.headers["Content-Type"],
                "Authorization": request.headers.get("Authorization"),
            }
            async with httpx.AsyncClient() as client:
                proxy = await client.post(f"{URI}/{path}", headers=headers, data=data)
            response = Response(content=proxy.content, status_code=proxy.status_code)
            return response
        except Exception as e:
            raise HTTPException(status_code=403, detail=str(e))

    #FOR GET
    @router.get("/proxy/{path:path}")
    async def proxy_get_request(*, path: str, request: Request) -&amp;gt; Any:
        try:
            URI = coil_uri
            if path.startswith("user/"):
                URI = coil_api_uri
            headers = {
                "Content-Type": request.headers.get("Content-Type", "application/x-www-form-urlencoded"),
                "Authorization": request.headers["Authorization"],
            }
            async with httpx.AsyncClient() as client:
                proxy = await client.get(f"{URI}/{path}", headers=headers)
            response = Response(content=proxy.content, status_code=proxy.status_code)
            return response
        except Exception as e:
            raise HTTPException(status_code=403, detail=str(e))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all you need on the server, but let me give you a little context and explanation.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@router.post("/proxy/{path:path}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;FastAPI uses &lt;code&gt;types&lt;/code&gt; and Pydantic to annotate variables. What's going to happen here is that all the text after &lt;code&gt;path&lt;/code&gt; is going to be treated as a &lt;code&gt;path&lt;/code&gt; type and provided as a variable to the function. We then use &lt;code&gt;httpx&lt;/code&gt; to make a new &lt;code&gt;post&lt;/code&gt; to Coil. Note, though, how we have to account for the gotcha of shifting endpoints.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;URI = coil_uri
if path.startswith("user/"):
    URI = coil_api_uri
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then we regenerate our headers, get any data from the request in &lt;code&gt;json&lt;/code&gt;/&lt;code&gt;dict&lt;/code&gt; format, and proxy using &lt;code&gt;httpx&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data = await request.json()
async with httpx.AsyncClient() as client:
    proxy = await client.post(f"{URI}/{path}", headers=headers, data=data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; Coil response codes are a little unclear. The only failure code is a &lt;code&gt;403&lt;/code&gt; request failure. You do get some text to explain the error, and you can send that back to the app, but I found it often quite obscure. It wasn't always clear what I had done wrong, or what wasn't working.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. Your subscriber authorises your right to monetize your content
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/piiV_Cma8MvnZvqYqlcY2_60UCHDcfazxeyDVfEVnuU/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzRlMXFjZzE2/b2p5b3NhM3R2aGZl/LnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/piiV_Cma8MvnZvqYqlcY2_60UCHDcfazxeyDVfEVnuU/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzRlMXFjZzE2/b2p5b3NhM3R2aGZl/LnBuZw" alt="monetization-4-monetization" width="701" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;At the end of this process you will have:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authorise your rights to monetize a subscriber,&lt;/li&gt;
&lt;li&gt;Initialise your web monetization stream, and specify a destination wallet,&lt;/li&gt;
&lt;li&gt;Refresh streaming tokens as required,&lt;/li&gt;
&lt;li&gt;Stop / Revoke streaming in response to your subscriber's wishes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Authorise your rights to monetize a subscriber
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;btpToken&lt;/code&gt; - which is a &lt;em&gt;promise&lt;/em&gt; to fulfill a payment request, not payment in and of itself - is only valid for 30 minutes. There is no point in generating one until your subscriber actually expresses their intention to stream money to your wallet.&lt;/p&gt;

&lt;p&gt;You'll need some sort of "start" / "stop" UX on your app, and the Web Monetization Foundation has a great set of docs that &lt;a href="https://webmonetization.org/docs/start-stop/" rel="noopener noreferrer"&gt;explain exactly this&lt;/a&gt;. You can review those after this section. First we use the subscriber's authentication token to request a BTP token.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;frontend&lt;/code&gt; store &lt;code&gt;./store/monetization/actions.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async getUserBTP({ commit, dispatch, state }) {
    try {
      const response: AxiosResponse = await coil.getUserBTP(state.coilToken)
      const data: ICoilUserBTP = response.data
      await commit("setBtpToken", data.btpToken)
    } catch (error) {
      await dispatch("checkCoilError", error)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Back in &lt;code&gt;frontend&lt;/code&gt; &lt;code&gt;./api/coil.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async getUserBTP(accessToken: string) {
    return await axios.post&amp;lt;ICoilUserBTP&amp;gt;(
      `${process.env.apiUrl}/api/v1/coil/proxy/user/btp`,
      {},
      requestUserHeaders(accessToken)
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;There's no point in storing the token outside of the store. It is valid for only 30 minutes from the time of issue, whether it is used or not.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; the request to get the &lt;code&gt;btpToken&lt;/code&gt; is both in camel-case, and a &lt;code&gt;post&lt;/code&gt;. It also has no &lt;code&gt;data&lt;/code&gt; to send in post, so ensure you include an empty object: &lt;code&gt;{}&lt;/code&gt;. It also uses &lt;code&gt;https://api.coil.com&lt;/code&gt;. Both the request to get user info, and BTP token are the only requests to this root endpoint.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;response&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "btpToken": "eyJhbGciOiJCchQ8...SeOz98I2Sqyf1LrVM"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now you have a &lt;code&gt;btpToken&lt;/code&gt;, but possession of a token is not money. That still needs to happen in the next process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initialise your web monetization stream, and specify a destination wallet
&lt;/h3&gt;

&lt;p&gt;We'll start with the gotcha.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOTCHA:&lt;/strong&gt; The challenge of websites is also the purpose of JavaScript web frameworks, like Node. To manage, and &lt;strong&gt;persist&lt;/strong&gt;, state. The more you can persist between state changes (shifting between different web pages) the more a website can feel like a native application. A Progressive Web App is all about near native user-experience. Problem is, the current Coil OWM script does not seem to expect this. Their documentation expressly states "Add the following code to the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; section of each page you are monetizing.":&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
    document.coilMonetizationPolyfill.init({ btpToken: 'USERS_TOKEN_HERE' })
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Doing that in a Node app, though, will instantly crash your app during transitions since this is exactly the sort of state that Node will preserve. That means initialising the &lt;code&gt;coilMonetizationPolyfill&lt;/code&gt; needs to look like this on every page:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (
  process.client &amp;amp;&amp;amp;
  Object.prototype.hasOwnProperty.call(document, "coilMonetizationPolyfill")
)
  try {
    document.coilMonetizationPolyfill.init({ btpToken: this.btpToken })
  } catch (error) {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In words: check that your script will execute &lt;em&gt;only&lt;/em&gt; client-side, and that the &lt;code&gt;coilMonetizationPolyfill&lt;/code&gt; has been initialised, and then &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt; to initialise it with the &lt;code&gt;btpToken&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I created a single "start" / "stop" component to listen to subscriber instructions.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;frontend&lt;/code&gt; &lt;code&gt;./components/monetization/MenuToggle.vue&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public setMonetizationTag() {
    // https://github.com/Techgethr/webmonvuejs/blob/main/index.js
    // https://webmonetization.org/docs/start-stop/
    const monetizationTag = document.querySelector('meta[name="monetization"]')
    if (!monetizationTag &amp;amp;&amp;amp; this.coilPointer) {
      const meta = document.createElement("meta")
      meta.name = "monetization"
      meta.content = this.coilPointer
      document.getElementsByTagName("head")[0].appendChild(meta)
    }
    if (monetizationTag &amp;amp;&amp;amp; !this.coilPointer) {
      monetizationTag.remove()
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ETHICS:&lt;/strong&gt; The way that the OWM script "listens" to whether or not a valid BTP token should be used to stream payments is whether or not there is a metatag pointing to a wallet. &lt;strong&gt;You end streaming payments by removing the pointer.&lt;/strong&gt; Everyone, from your subscriber, to Coil, to the Web Monetization Foundation and Grant for the Web are relying on you to honour your subscriber's intentions and expressed instructions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When your subscriber tells you to stream, you add the wallet pointer:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (!monetizationTag &amp;amp;&amp;amp; this.coilPointer) {
  const meta = document.createElement("meta")
  meta.name = "monetization"
  meta.content = this.coilPointer
  document.getElementsByTagName("head")[0].appendChild(meta)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And when they tell you to stop, you remove it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (monetizationTag &amp;amp;&amp;amp; !this.coilPointer) {
  monetizationTag.remove()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That's the entirety of your subscriber's protection from abuse of their trust in you. That you &lt;strong&gt;MUST&lt;/strong&gt; honour their instructions and delete the wallet pointer. Please - and I know I'm whispering in a thunderstorm - please don't abuse this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Refresh streaming tokens as required
&lt;/h3&gt;

&lt;p&gt;"Start" and "Stop" is equivalent to a call to check the validity of a token, and refresh them as required.&lt;/p&gt;

&lt;p&gt;In the same &lt;code&gt;frontend&lt;/code&gt; component &lt;code&gt;./components/monetization/MenuToggle.vue&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public async toggleMonetizationStreaming() {
    if (!this.isStreaming) await this.startMonetizationStream()
    else await this.stopMonetizationStream()
    this.setMonetizationTag()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is in response to the subscriber toggling a button in the menu bar of the app.&lt;/p&gt;

&lt;p&gt;It is your responsibility to check that a BTP token is still valid. A JWT token intrinsically contains the time it was created, so you can easily check.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;frontend&lt;/code&gt; &lt;code&gt;./utilities/keys.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function getTimeInSeconds(): number {
  // https://stackoverflow.com/a/3830279/295606
  return Math.floor(new Date().getTime() / 1000)
}

function tokenExpired(token: string) {
  // https://stackoverflow.com/a/60758392/295606
  const expiry = JSON.parse(atob(token.split(".")[1])).exp
  return getTimeInSeconds() &amp;gt;= expiry
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And then on to the &lt;code&gt;frontend&lt;/code&gt; store, where we manage monetization state &lt;code&gt;./store/monetization/actions.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  async startMonetizationStream({ commit, dispatch, state }) {
    await dispatch("refreshUserBTP")
    if (!state.requestError) {
      await commit("setIsStreaming", true)
      await commit("setCoilPointer", process.env.coilPointer as string)
      await commit(
        "main/addNotification",
        {
          content: "You have started web monetization.",
          color: "success",
        },
        { root: true }
      )
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refreshing the BTP token triggers a cascade of requests. I felt there was no point in worrying about the difference between an &lt;code&gt;authentication_token&lt;/code&gt; valid for 60 minutes, and a &lt;code&gt;btpToken&lt;/code&gt; valid for 30. Just refresh both of them.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;frontend&lt;/code&gt; &lt;code&gt;./store/monetization/actions.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async refreshUserBTP({ commit, dispatch, state }) {
    if (getCoilRefresh()) {
      try {
        const response: AxiosResponse = await coil.getUserRefresh(
          getCoilRefresh() as string
        )
        const data: ICoilUserTokenResponse = response.data
        // https://stackoverflow.com/a/32108184/295606
        if (
          data &amp;amp;&amp;amp;
          Object.keys(data).length !== 0 &amp;amp;&amp;amp;
          data.constructor === Object
        ) {
          await commit("setCoilToken", data.access_token)
          saveCoilRefresh(data.refresh_token)
          if (!state.requestError) {
            await dispatch("getUserBTP")
            await commit("setRequestError", false)
          }
        } else {
          await commit("setRequestError", true)
        }
      } catch (error) {
        await dispatch("checkCoilError", error)
      }
    } else {
      await commit("setRequestError", true)
      await commit(
        "main/addNotification",
        {
          content: "Error authorising web monetization.",
          color: "error",
        },
        { root: true }
      )
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You'll note that there are profoundly numerous points in the process where things can go wrong and &lt;code&gt;try&lt;/code&gt; / &lt;code&gt;catch&lt;/code&gt; is triggered.&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;frontend&lt;/code&gt; api request in &lt;code&gt;./api/coil.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async getUserRefresh(refreshToken: string) {
    const data: ICoilUserTokenRefreshRequest = {
      refresh_token: refreshToken,
      grant_type: "refresh_token",
      scope: "simple_wm openid",
    }
    return await axios.post&amp;lt;ICoilUserTokenResponse&amp;gt;(
      `${process.env.apiUrl}/api/v1/coil/proxy/oauth/token`,
      data,
      requestHeaders()
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With that you can get your BTP token, as described above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stop / Revoke streaming in response to your subscriber's wishes
&lt;/h3&gt;

&lt;p&gt;Technically, all that's required to stop the process is to remove the wallet pointer in the metatag.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;frontend&lt;/code&gt; &lt;code&gt;./store/monetization/actions.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async stopMonetizationStream({ commit }) {
    await commit("setIsStreaming", false)
    await commit("setCoilPointer", "")
    await commit(
      "main/addNotification",
      {
        content: "You have stopped web monetization.",
        color: "success",
      },
      { root: true }
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Cascading back up the code, you call &lt;code&gt;this.setMonetizationTag()&lt;/code&gt; which removes the metatag pointer.&lt;/p&gt;

&lt;p&gt;From the &lt;code&gt;frontend&lt;/code&gt; component &lt;code&gt;./components/monetization/MenuToggle.vue&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (monetizationTag &amp;amp;&amp;amp; !this.coilPointer) {
  monetizationTag.remove()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Revoking&lt;/strong&gt; is slightly more convoluted. If a subscriber revokes, you need their &lt;code&gt;authentication_token&lt;/code&gt; to perform the action. However, their token may no longer be valid, so you may need to first refresh their token and only &lt;em&gt;then&lt;/em&gt; revoke your access to their keys.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;frontend&lt;/code&gt; &lt;code&gt;./store/monetization/actions.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async revokeMonetization({ commit, dispatch, state }) {
    await dispatch("stopMonetizationStream")
    if (state.coilToken) {
      await coil.revokeUserResource(state.coilToken)
      await commit("setCoilToken", "")
    } else if (getCoilRefresh()) {
      // Counterintuitively, we need a new user_token to request the revoke
      try {
        const response: AxiosResponse = await coil.getUserRefresh(
          getCoilRefresh() as string
        )
        const data: ICoilUserTokenResponse = response.data
        if (
          data &amp;amp;&amp;amp;
          Object.keys(data).length !== 0 &amp;amp;&amp;amp;
          data.constructor === Object
        )
          await coil.revokeUserResource(data.access_token)
      } catch (error) {
        await dispatch("checkCoilError", error)
      }
    }
    if (getCoilRefresh()) removeCoilRefresh()
    await commit(
      "main/addNotification",
      {
        content: "You have successfully revoked web monetization.",
        color: "success",
      },
      { root: true }
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With the &lt;code&gt;frontend&lt;/code&gt; api request in &lt;code&gt;./api/coil.ts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async revokeUserResource(token: string) {
    const data: ICoilUserTokenRevokeRequest = {
      token,
    }
    return await axios.post(
      `${process.env.apiUrl}/api/v1/coil/proxy/oauth/token/revocation`,
      data,
      requestHeaders()
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;There is no response to a revoke request. However, remember we adopted an ethical design approach. If our subscriber has deliberately cleared their cookies, we no longer have access to their &lt;code&gt;refresh_token&lt;/code&gt; anyway, and so no longer have access to their account.&lt;/p&gt;




&lt;p&gt;And that's it (so far). There's more we can do ... split payments (proportionally specifying which wallet pointer is monetized), opening up sections of your app to streaming subscribers, etc. All of that requires a rock-solid payment process, and I hope this guide helps save you at least some time and frustration in achieving that.&lt;/p&gt;

&lt;p&gt;Please let me know in the comments what you think, what I missed, and what can be fixed / improved.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;For both this guide, and for those of you wishing to go further and add things like scheduled token refresh, proportional payments, etc.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://help.coil.com/docs/dev/web-monetization" rel="noopener noreferrer"&gt;Coil Web Monetization documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webmonetization.org/docs/api" rel="noopener noreferrer"&gt;Web Monetization documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://javascript.info/settimeout-setinterval" rel="noopener noreferrer"&gt;Scheduling: setTimeout and setInterval&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>grantreports</category>
      <category>publishing</category>
      <category>ethics</category>
      <category>scifi</category>
    </item>
    <item>
      <title>Qwyre.com - What does a minimum lovable product even look like?</title>
      <dc:creator>Gavin Chait</dc:creator>
      <pubDate>Tue, 10 Aug 2021 16:20:12 +0000</pubDate>
      <link>https://community.interledger.org/qwyre/qwyre-com-what-does-a-minimum-loveable-product-even-look-like-22bm</link>
      <guid>https://community.interledger.org/qwyre/qwyre-com-what-does-a-minimum-loveable-product-even-look-like-22bm</guid>
      <description>&lt;p&gt;One of the management-speak fads of the last decade has been the &lt;a href="https://en.wikipedia.org/wiki/Minimum_viable_product" rel="noopener noreferrer"&gt;minimum viable product&lt;/a&gt; which, instinctively, we probably all know is wrong. It leaves us with scaffolding and dodgy wiring. Made things that are difficult to use, fail easily, and add to the noise and clutter of the world.&lt;/p&gt;

&lt;p&gt;They can be important early steps, but they're difficult to "love". And, if what we want is people to switch to wanting to use what we make, we need to offer at least a &lt;em&gt;modicum&lt;/em&gt; of enjoyment to these fledgling efforts.&lt;/p&gt;

&lt;p&gt;This approach is the &lt;strong&gt;minimum lovable product&lt;/strong&gt;. Unlike MVP, MLP doesn't seem to have an established parent, but the earliest visual representation of this I can find is from Jussi Pasanen:&lt;/p&gt;

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

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



&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.crisp.se/2016/01/25/henrikkniberg/making-sense-of-mvp" rel="noopener noreferrer"&gt;Henrik Kniberg&lt;/a&gt;, writing in 2016, presented a more visual way or representing this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/7fL50Inbm-iZ7w5GquE_CvuHH5A-lk4alG-LmlGGdcU/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzVnbTBhdHU3/YXdrN3JhNW1nNGp4/LnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/7fL50Inbm-iZ7w5GquE_CvuHH5A-lk4alG-LmlGGdcU/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzVnbTBhdHU3/YXdrN3JhNW1nNGp4/LnBuZw" alt="Minimum lovable product, Henrik Kniberg" width="800" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Problem is, this implies you know what all of these intermediate things are when all you've got is this wheel you invented. Except ... none of us invented a wheel in a world of pedestrians. We're iterating on what exists.&lt;/p&gt;

&lt;p&gt;In the case of Qwyre, that means there are already many ways to convert a document to an epub, and multiple ways to publish them. I know what my iteration &lt;a href="https://community.webmonetization.org/qwyre/qwyre-com-a-framework-for-ethics-and-technical-development-in-fiction-publishing-3nln" rel="noopener noreferrer"&gt;looks like at the end&lt;/a&gt;, but I want an interim release, and that release needs to be useful.&lt;/p&gt;

&lt;p&gt;Useful to me so I can get feedback and test how webmonetization works in production, but definitely useful to people so that they want to give me that feedback in the first place.&lt;/p&gt;

&lt;p&gt;Originally, I thought I'd go as far as releasing an epub converter and call that &lt;em&gt;interim&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/kEUJGLB8d9Qn4Ge_Dn3DHzYMZ8JlEN-258eaUqF1Tsk/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL3k4Z2RuN2I1/ODd6ZTN1NXc0djZ0/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/kEUJGLB8d9Qn4Ge_Dn3DHzYMZ8JlEN-258eaUqF1Tsk/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL3k4Z2RuN2I1/ODd6ZTN1NXc0djZ0/LmpwZw" alt="Qwyre.com Epub conversion step" width="571" height="760"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The more I sat with that, though, the less enjoyable that became. The process of conversion is easy-enough, and the design - while interim - is inoffensive. I'm a professional writer, and this is also a tool I'd like to use. &lt;/p&gt;

&lt;p&gt;But ... my workflow, using my own app, was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate the epub,&lt;/li&gt;
&lt;li&gt;Download it,&lt;/li&gt;
&lt;li&gt;Import it into Calibre,&lt;/li&gt;
&lt;li&gt;Check that it looks ok.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's a lot of effort, and distinctly displeasing. I can't ask anyone else to struggle that way.&lt;/p&gt;

&lt;p&gt;I decided that the interim app needs to have a functioning ereader. But you can't have a MVP ereader in this here 2021. It has to be an ereader. Which led me to the rabbit-hole of Epub.js, still the only mostly-functioning open-source browser-based ereader library:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/futurepress" rel="noopener noreferrer"&gt;
        futurepress
      &lt;/a&gt; / &lt;a href="https://github.com/futurepress/epub.js" rel="noopener noreferrer"&gt;
        epub.js
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Enhanced eBooks in the browser.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;There is &lt;em&gt;documentation&lt;/em&gt;, but no actual developer manual, and definitely no tutorials. The API appears to have changed without notice, so what works in other packages doesn't work anymore. It's a bit tedious. I'm also a little nervous as to how large it is, but I'll see how that goes once I get to the build point.&lt;/p&gt;

&lt;p&gt;A large-scale digression over the last five weeks means this is possible:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/L2kpkRSms9l2d2C6_2RDUWP2vClTfuyriPG0T20Ottg/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL20zdm1oMTdy/Y3JlcThjYTd6cDR0/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/L2kpkRSms9l2d2C6_2RDUWP2vClTfuyriPG0T20Ottg/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL20zdm1oMTdy/Y3JlcThjYTd6cDR0/LmpwZw" alt="Qwyre.com Epub reader, showing the menu page for 'Our Memory Like Dust'" width="574" height="766"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The app is a &lt;a href="https://web.dev/progressive-web-apps/" rel="noopener noreferrer"&gt;Progressive Web App&lt;/a&gt;, and I'm using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API" rel="noopener noreferrer"&gt;IndexedDB&lt;/a&gt; to store data in the browser to make the user-experience truly offline. You can store all your epubs in the app, just like a real reader, and it is fully mobile responsive, meaning swipes and taps work as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/eDW90RJaUDa8C2WL34Svx0l6GZ2ibaHdHRb4XkauEJg/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL29lMnFlYnVy/ano5dGpmODdmZGgw/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/eDW90RJaUDa8C2WL34Svx0l6GZ2ibaHdHRb4XkauEJg/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzL29lMnFlYnVy/ano5dGpmODdmZGgw/LmpwZw" alt="Qwyre.com landing page showing epub and conversion libraries" width="574" height="766"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My workflow now is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Upload and convert a doc to epub,&lt;/li&gt;
&lt;li&gt;Read my epub...&lt;/li&gt;
&lt;li&gt;Get lost importing and reading other ebooks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which is the way it's supposed to be.&lt;/p&gt;

&lt;p&gt;The app itself works fine for release, but I still want to get basic webmonetization implemented, and then it should be ready to deploy for interim release. I've just picked up another project, so I'm going to be delayed for a few weeks, but hopefully by early September.&lt;/p&gt;

&lt;p&gt;One thing I'd like to know, though (for those who read this far) ... what is your favourite ereader app (not device), and what features makes it special for you?&lt;/p&gt;

</description>
      <category>grantreports</category>
      <category>publishing</category>
      <category>ethics</category>
      <category>scifi</category>
    </item>
    <item>
      <title>Qwyre.com - a framework for ethics and technical development in fiction publishing</title>
      <dc:creator>Gavin Chait</dc:creator>
      <pubDate>Wed, 30 Jun 2021 17:53:26 +0000</pubDate>
      <link>https://community.interledger.org/qwyre/qwyre-com-a-framework-for-ethics-and-technical-development-in-fiction-publishing-3nln</link>
      <guid>https://community.interledger.org/qwyre/qwyre-com-a-framework-for-ethics-and-technical-development-in-fiction-publishing-3nln</guid>
      <description>&lt;p&gt;Two months after my dad died, I was sitting on a beach in the Philippines writing my first novel. My dad's passing was expected. My writing trip long-planned. It's the sequence that wasn't the one I had hoped.&lt;/p&gt;

&lt;p&gt;I wrote 2,000 words a day for a month, and, when it was done, the orbital prison of Tartarus and the remote Nigerian village of Ewuru were real.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://gavinchait.com/w/u7sfZq9nL2yh/" rel="noopener noreferrer"&gt;&lt;strong&gt;Lament for the Fallen&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Isolated and with his orbital city hiding in the rubble of a devastating war, Samara falls 35,000km to escape from the space-based prison of Tartarus, smashing into the jungle near an isolated Nigerian village. Struggling to heal, and hunted by a brutal warlord in a ruthless land, Samara searches for a way home to the woman he loves.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;iframe src="https://open.spotify.com/embed/playlist/5gUE1sa6B18iE7jNm58kfP" width="100%" height="380px"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I never expected an agent or publishing contract, didn't want to rely on Amazon, and so &lt;a href="https://gavinchait.com" rel="noopener noreferrer"&gt;I rolled my own&lt;/a&gt;. Within a few months, my long-term software roadmap became moot when Penguin Random House picked up LAMENT for publication. The world of creative self-publishing stops at the border of formal publication.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/turukawa" rel="noopener noreferrer"&gt;
        turukawa
      &lt;/a&gt; / &lt;a href="https://github.com/turukawa/whyqd" rel="noopener noreferrer"&gt;
        whyqd
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Whyqd is an extensible object-based wiki bringing revision control, content presentation, forking and embedding to any type of digital object.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;A publishing house must have a list of titles, each within a set of genres conforming to the topics in the average bookshop. They make their money off their handful of giants, and do little to promote or develop the careers of their &lt;em&gt;mid-list&lt;/em&gt; writers. You are expected to promote your own work, but you can't sell your own work. Twitter ok. Experimental self-publishing definitely not.&lt;/p&gt;

&lt;p&gt;The challenge with &lt;em&gt;lists&lt;/em&gt; and &lt;em&gt;genres&lt;/em&gt; is that success is a game of whack-a-mole. Success leads to copycats, leads to perpetuation of specific norms. For African writers, this has meant we need to conform to an industry notion satirised by Binyavanga Wainaina in his frustrated guide on &lt;a href="https://granta.com/how-to-write-about-africa/" rel="noopener noreferrer"&gt;&lt;em&gt;How to Write About Africa&lt;/em&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Never have a picture of a well-adjusted African on the cover of your book, or in it, unless that African has won the Nobel Prize. An AK-47, prominent ribs, naked breasts: use these. If you must include an African, make sure you get one in Masai or Zulu or Dogon dress.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A continent of 54 countries, countless ethnicities, and over a billion people reduced to a cartoon cameo in the mid-list.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/c-jSQD5FVxE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  A framework for African collaborative publishing
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Umumtu ngumuntu ngabangtu&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The African philosophy of &lt;em&gt;Ubuntu&lt;/em&gt; can be summarised as "I am because we are." As I writer, I am both reader and writer because there are other writers. There is no line between writer and reader just as there is no line between genres, stories and influences.&lt;/p&gt;

&lt;p&gt;Qwyre starts with the following simple principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All Qwyre users are creators,&lt;/li&gt;
&lt;li&gt;A creator can perform any task of:

&lt;ul&gt;
&lt;li&gt;Reading,&lt;/li&gt;
&lt;li&gt;Writing,&lt;/li&gt;
&lt;li&gt;Editing,&lt;/li&gt;
&lt;li&gt;Curating.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The path to publication requires appointing an editor and letting them decide when a creative work is ready,&lt;/li&gt;

&lt;li&gt;Editors and curators are first-class citizens in the creative process and should be paid,&lt;/li&gt;

&lt;li&gt;An forum of peers is there to resolve disputes,&lt;/li&gt;

&lt;li&gt;Popularity can be gamed and is no guide to what a reader may enjoy,&lt;/li&gt;

&lt;li&gt;All writers should have an equal shot at being read.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;In practice, this implies &lt;em&gt;moderated publication&lt;/em&gt;. There is no gatekeeping. We do not assign an editor and decide who gets published. All we require is that a writer recruit and appoint an editor, and that their editor gets to decide when their work is ready for publication. This will require an appeals process, and so we need an editors forum.&lt;/p&gt;

&lt;p&gt;This leads to the following structured components to support a Qwyre workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EPUB generator: convert a &lt;code&gt;.docx&lt;/code&gt; file to a standards-compliant &lt;code&gt;.epub&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;Editor recruitment: manage the recruitment and contractual agreement between a writer and editor,&lt;/li&gt;
&lt;li&gt;eReader: an online standards-compliant EPUB reader,&lt;/li&gt;
&lt;li&gt;Web monetisation: books can be read by streaming and, once paid for via monetisation, can be downloaded as an epub,&lt;/li&gt;
&lt;li&gt;Editor's forum: a formal review process to permit dispute resolution between writers and their editors,&lt;/li&gt;
&lt;li&gt;Curation: create lists of works which can be shared and promoted,&lt;/li&gt;
&lt;li&gt;Search and discovery: a neutral, automated method to categorise and assess creative works to support reader discovery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this time, given the diverse needs of writers and their preferred ways of working, Qwyre will not offer any online editing or writing support. These are to take place offsite.&lt;/p&gt;

&lt;p&gt;Where possible, components will be released as free-standing apps as they are developed. The most immediately obvious of these are the EPUB generator, and the ereader app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nuxt.js and FastAPI as our technical stacks
&lt;/h2&gt;

&lt;p&gt;Qwyre will be a progressive web app (PWA) with offline support for the reader's library of ebooks. Books can be bought as epubs, or streamed via web monetisation. What a reader "buys" via web monetisation, they own. Once they have streamed an entire book, they are able to download an epub version of the work.&lt;/p&gt;

&lt;p&gt;I have developed on Django for years and enjoyed it, and its documentation. Recently, however, I've wanted to try something new and more in line with my current standards-based approach to data curation. Python's support for types has evolved, and the most modern and mature framework for working with Pydantic is &lt;a href="https://fastapi.tiangolo.com/" rel="noopener noreferrer"&gt;FastAPI&lt;/a&gt; which comes with a Docker base project with PostreSQL and Vue.js. I didn't want to faff too much with Vue, and set out to produce a version for &lt;a href="https://nuxtjs.org/" rel="noopener noreferrer"&gt;Nuxt.js&lt;/a&gt; (which is to Vue what Next is to React) as well as replacing Vuetify with &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt; for the user interface design. &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/whythawk" rel="noopener noreferrer"&gt;
        whythawk
      &lt;/a&gt; / &lt;a href="https://github.com/whythawk/nuxt-for-fastapi" rel="noopener noreferrer"&gt;
        nuxt-for-fastapi
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Nuxt.js frontend drop-in replacement for Vue.js FastAPI base project generator.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;This provides a solid starting point for further development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current development: Chapisha and EPUB Generator PWA
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Chapisha&lt;/strong&gt; provides an intuitive method for converting a well-formatted Microsoft Word &lt;code&gt;.docx&lt;/code&gt; file into a standards-compliant EPUB3 ebook. I developed the standalone Python package to support a Nuxt.js progressive web application.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/whythawk" rel="noopener noreferrer"&gt;
        whythawk
      &lt;/a&gt; / &lt;a href="https://github.com/whythawk/chapisha" rel="noopener noreferrer"&gt;
        chapisha
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Python-based docx to standards-compliant epub3 conversion
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The core functionality for the EPUB convertor is almost complete.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.interledger.org/images/eQ3gVOpGA4ufYRlVoQrhyaJzJw47WFhai1pYLpgZ5zM/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzk4ejJwZHd2/OW1iM3hhY2ZlYW15/LmpwZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.interledger.org/images/eQ3gVOpGA4ufYRlVoQrhyaJzJw47WFhai1pYLpgZ5zM/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkuaW50ZXJs/ZWRnZXIub3JnL3Jl/bW90ZWltYWdlcy91/cGxvYWRzL2FydGlj/bGVzLzk4ejJwZHd2/OW1iM3hhY2ZlYW15/LmpwZw" alt="A screenshot of the current development landing page for Qwyre.com" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next up is to work with &lt;a href="https://help.coil.com/docs/dev/web-monetization/index.html" rel="noopener noreferrer"&gt;Coil's web monetisation API&lt;/a&gt; and test streaming payments to support EPUB conversion. The specific mechanism is that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A creator provides all the content and metadata required to generate an EPUB in the PWA,&lt;/li&gt;
&lt;li&gt;Data only transfer from the creator to the Qwyre server during the generation process,&lt;/li&gt;
&lt;li&gt;During generation, they can stream payments to support the app,&lt;/li&gt;
&lt;li&gt;Streaming is monitored directly using websockets giving the creator feedback on the generation process, and costs,&lt;/li&gt;
&lt;li&gt;On completion, all creator data is deleted from the server and returned to the creator.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm hoping to complete this in mid-July and deploy a test server with the EPUB generator for public testing and use by the end of July 2021. In my next update, I'll hopefully have a link for you to go test what I built so far.&lt;/p&gt;

&lt;p&gt;I'm keen on feedback and discussion, so please get in contact and let me know your thoughts or concerns.&lt;/p&gt;

</description>
      <category>grantreports</category>
      <category>publishing</category>
      <category>ethics</category>
      <category>scifi</category>
    </item>
  </channel>
</rss>
