Skip to main content
Back to Blog
Sticker — July 19, 2024

Building the Grin Payment Plugin from Scratch

By John Murphy

Grin is a Mimblewimble cryptocurrency — minimal scripting surface, no addresses, transactions assembled interactively between sender and receiver. That last property is exactly what makes it interesting and exactly what makes integrating it with a checkout page non-trivial.

Why bother

Privacy-preserving crypto is a small market, but it's a market where the people who care really care, and they're paying customers who don't have a lot of merchant options. There's no Stripe button for Grin. If you want to sell stuff and accept Grin, somebody has to write the plugin.

The slatepack flow

Grin transactions are built in rounds:

1. Sender creates a half-built transaction (a slatepack) and sends it to the receiver. 2. Receiver fills in their half and returns it. 3. Sender finalizes it and broadcasts to the network.

The plugin runs a checkout flow around that:

1. Customer hits "Pay with Grin" → backend generates an invoice and shows a slatepack address + amount. 2. Customer pastes a slatepack from their wallet into the checkout page. 3. The checkout server (a small standalone Node service we run alongside Medusa) finalizes the transaction and broadcasts it. 4. As soon as the broadcast happens, the checkout server fires a webhook back to Medusa, which creates the order and redirects the customer to the success page. 5. After the configured number of confirmations (10 by default, ~10 minutes for Grin), Medusa fires `payment.captured` and the tenant gets a "safe to ship" email.

The webhook pipeline

Medusa v2 has a payment-provider plugin contract; the Grin provider is a thin wrapper around the Grin-checkout REST API. The interesting bits are:

  • Order creation on broadcast, not confirmation. Waiting for 10 confirmations before the customer sees a success page is a terrible UX. The order is created on broadcast and shows a "confirming" badge in the tenant admin until the network catches up.
  • Auto-redirect on detection. The customer's browser polls the checkout status endpoint and switches to the order-confirmation page the moment the broadcast happens. This was v1.0.9.
  • HMAC-signed webhooks. Both directions verify the raw request body — re-serializing JSON before HMAC-ing is a classic way to get an off-by-one signature mismatch. Took an evening to debug the first time.

What was hard

The wallet-side ergonomics. There's no "address" to put in the cart. The customer has to actively use their wallet to participate in the transaction. Grin users mostly understand this; that doesn't mean we can ship a checkout that requires understanding it.

The compromise: clear instructions, a copy-button on the slatepack, and a polled status indicator that updates in real time. People who already use Grin recognize the flow immediately. People who don't will need a bit of reading.

Where it lives

The plugin is published to npm and the source is the same code that runs Such Shop's Grin button right now. Reach out if you want to use it.