project screenshot
project screenshot
project screenshot
project screenshot
project screenshot

xPay: Cross-Chain Payments for Shopify

Accept payments in crypto in your Shopify store. For your user, the freedom to pay with their favorite token, on their favorite chain. For you, the simplicity to just receive USDC on Polygon.

xPay: Cross-Chain Payments for Shopify

Created At

ETHSanFrancisco 2022

Winner of


🥉 0x — Best Use


5️⃣ Covalent — Best Use

Project Description

We run the store, powered by Shopify. For years we've been looking at accepting payments in crypto, we're sure many other merchants want the same. We know our users want to pay with a variety of tokens, on different chains. As a merchant, we don't want to deal with the complexity of receiving too many different things. We just want USDC (stable) on Polygon (fast, cheap). Enter xPay. Merchant flow: First we registered a merchant account on the xPay merchant UI and added our Polygon wallet address. Next, in our Shopify admin we added xPay as a manual payment method and configured it according to the instructions given to us in the xPay merchant UI. User flow: The user completes the checkout and selects xPay as payment method (instead of credit card). The user is then sent to the xPay website, where they 1) select the chain, 2) connect their wallet, 3) select a token to pay, 4) click pay and submit a blockchain tx. From the user's perspective, the payment is done. Behind the scenes: 1) the user just submitted a wormhole transaction, 2) the xPay frontend receives the wormhole VAA and sends it to the xPay backend, 3) xPay backend stores this info in Firebase db, 4) an xPay backend poller sees a new VAA and completes the wormhole transfers (tx on Polygon chain), 5) the poller then interacts with xPay smart contract (2nd tx on Polygon) that does a swap into USDC, followed by a USDC.transfer to the merchant address. Future works: 1) combine wormhole complete transfer, swap into USDC and USDC transfer into a single smart contract. 2) generalize Polygon > merchant favorite chain.

How it's Made

There are 4 projects in our monorepo: the Merchant UI, the Customer Payment UI, the backend poller, and our smart contract.

Primer - How Shopify Manual Payments work: Shopify recently deprecated custom checkout apps in favor of their own Shopify checkout system. There are still 3rd party checkout apps on Shopify, but these are specially whitelisted by Shopify (and invite-only – there is no form to apply). Therefore, to integrate with Shopify we needed to build a manual payment method.

The Shopify manual payment method is very manual. Once a user completes a checkout flow with a manual payment method, they are simply shown text instructions on how to complete the payment. Once the merchant has deemed the payment complete, they manually mark the order as completed in their Shopify admin page. This means that manual payment methods are entirely offline and have no programmatic communication with the Shopify infrastructure. We therefore had to build around these limitations to offer an experience that is as seamless as possible for both the merchant and customer.

Merchant UI: The merchant UI is a react frontend currently hosted at We use Firebase auth for user management. When a merchant signs up, the first thing they need to do is generate a merchant ID. This merchant ID is used by our customer payment UI and disbursement logic to tie payments to specific merchants (and ultimately direct USDC to the correct address on polygon). The merchant also needs to provide their polygon wallet address. After the merchant has generated their ID, they are shown instructions on how to add xPay as a manual payment method on Shopify. They can also view the completed polygon disbursement transactions (tied with their order IDs) so they can mark orders as completed in Shopify.

Customer Payment UI: This UI was originally forked from the wormhole portal bridge UI (which is why it looks so nice). We removed the unnecessary features and added several of our own. It is a React App currently hosted at

The way it works is merchants instruct their customers to navigate to a url such as{merchantId} to pay with xPay. As soon as the user navigates to this url, the merchant ID is saved in the frontend state. The user then goes through the following steps:

  1. Select the source chain and source token to pay with. a. Enter the USD amount to pay the merchant (must be manually entered b/c of lack of API integration with Shopify). b. Enter the Order ID shown on their completed Shopify checkout page (must be manually entered b/c of lack of API integration with Shopify) The user needs to connect their wallet while selecting the source chain/token. Behind the scenes, our UI is using the wallet's RPC provider to query the blockchain.

  2. Connect their wallet to Polygon. Again, even though we have hardcoded the destination chain/address, we use the user wallet's RPC provider to query Polygon for information about the WH wrapped token that will be minted for them. Once we have information about the token, we query the 0x swap API to get a quote to determine the USD/targetAsset conversion rate. We query with an amount that is 2.9% higher so that we cover our Polygon gas costs and hedge FX risk (it's also a payments industry standard.. but this is really naive and can be optimized much better).

  3. Send the WH transaction on the source chain they selected. This is the only transaction that the customer needs to perform. After this step is completed, the transaction is over from the customer's perspective. Behind the scenes, we make an insertion into Firebase once the transaction on the source chain is completed. In firebase, we store the source transaction hash, the chain ID, merchant ID, and order ID, with a status of "CREATED".

It is important to note that for this MVP, the wormhole transfer directs bridged funds to our own address on Polygon. We then perform the 0x swap and disbursement to the merchant from this address (work which is triggered by the backend poller).

Backend Poller: This is a Node.js app that moves "CREATED" transactions through a state machine.

"CREATED" -> "SIGNED": This step queries the wormhole guardian network to see if the guardians have produced a signed assertion of the cross chain transfer (“VAA”) for the source transfer. If the guardians have produced the VAA, we store the VAA bytes in Firebase and update the status to "SIGNED".

"SIGNED" -> "SUBMITTED": This step relays the signed VAA to Polygon to mint the Wormhole wrapped asset. We use the Wormhole JS SDK to submit the VAA to the Polygon token bridge contract, which verifies the VAA and then mints wormhole-wrapped assets to our poller address. We then store the transaction hash of the VAA submission in firestore and update the status to "SUBMITTED".

"SUBMITTED" -> "COMPLETED": This step calls our Polygon contract to perform a 0x swap for USDC and send the USDC to the merchant's address. Behind the scenes, we are doing the following:

  1. Call 0x swap API for an up to date quote.
  2. Call our contract, which calls into the 0x swap.
  3. Call our contract, which sends the USDC to the merchant. Ideally, steps 2 & 3 should be batched into a single contract call, but we ran into issues with the original contracts we deployed which batched these functions. After these steps are completed, we save the swap and usdc disbursement transaction hashes to firebase and update the transaction status to "COMPLETED". From the merchant's perspective, this transaction is now completed (and it will pop up in their transactions table in the merchant UI).

Overall, this poller was what we had wanted to build for our MVP to keep things simple. Looking forward, we want to deprecate the poller in favor of a purely smart-contract dictated flow. We'd like to deploy an improved contract to Polygon which handles 1) calling the wormhole token bridge contract to verify the VAA and mint the wrapped tokens, 2) performing the 0x swap, and 3) sending the USDC to the merchant. Then, we could use wormhole generic relayers (soon to be released!) to call this contract in a fully decentralized way. This way we can cut our poller and relaying address completely out of the flow.

Smart Contract: We want to eventually make this close to trustless so we made a Polygon smart contract. Our poller tool will get a 0x quote and submit it to our smart contract. Our contract will call 0x’s exchange proxy directly to then do the swap. The contract can then take the newly swapped USDC and send it directly to the merchant atomically. For our submission we ran into solidity errors we couldn’t resolve directly but worked around it by splitting up our contract interaction into two transactions. We plan to fix this issue later so we only need one transaction. We’ve found that Polygon is pretty fast and the gas is cheap which afforded us to be more flexible.

On a next iteration, we’d like the merchant to register directly on the smart contract and fully remove our poller and just rely on Wormhole, 0x, and our near trustless contract.

background image mobile

Join the mailing list

Get the latest news and updates