Onramp with Monerium
Monerium (opens in a new tab) provides EURe, a regulated stablecoin on Ethereum, Polygon, and Gnosis. This guide demonstrates how to use the Monerium SDK (opens in a new tab) and the Safe{Core} SDK together to enable direct transfers from Safe accounts to an IBAN via the SEPA network and vice versa.
Prerequisites
- Node.js and npm (opens in a new tab)
- Monerium account and application (opens in a new tab)
- A web application using your favorite CLI and language. For example React with NextJS (opens in a new tab), Vue with Nuxt (opens in a new tab) or Svelte with SvelteKit (opens in a new tab).
- A deployed Safe for your users.
Overview
The main steps of this tutorial are:
- Authentication with Monerium and Safe: This requires signing a message with the Safe and using the Monerium Authentication flow.
- Sending an order from the Safe to an IBAN involves sending an order to Monerium and signing the order with the Safe.
Step 1: Authenticate with Monerium and Safe
Sign the link message with the Safe
First, your users have to sign a message with the Safe to prove ownership of the Safe. Monerium will scan the signed messages in the Safe to verify ownership.
Use the Safe{Core} SDK, as shown below, to sign a message with multiple owners (for example, a two-out-of-three Safe). This example programmatically signs a message with two owners. In practice, one owner proposes the transaction, and another owner confirms it using the Safe{Wallet} (opens in a new tab) UI.
_52import Safe, {_52 getSignMessageLibContract,_52 hashSafeMessage_52} from '@safe-global/protocol-kit'_52import { constants } from '@monerium/sdk'_52_52// Initialize the Safe\{Core\} SDK and link it to an existing Safe_52const protocolKit = await Safe.init({_52 provider: RPC_URL, // set a valid RPC URL_52 signer: OWNER_1_PRIVATE_KEY, // set the private key of the first Safe owner_52 safeAddress_52})_52_52// Create a signed message by creating a transaction to the signMessage contract_52const signMessageContract = await getSignMessageLibContract({_52 safeProvider: protocolKit.getSafeProvider(),_52 safeVersion: await protocolKit.getContractVersion()_52})_52_52// Let the contract encode the message's hash to get the transaction data_52const txData = signMessageContract.encode('signMessage', [_52 hashSafeMessage(constants.LINK_MESSAGE) // 'I hereby declare that I am the address owner.'_52])_52_52// Assemble a transaction object_52const safeTransactionData = {_52 to: await signMessageContract.getAddress(),_52 value: '0',_52 data: txData,_52 operation: OperationType.DelegateCall_52}_52_52// Create a transaction_52const signMessageTx = await protocolKit.createTransaction({_52 transactions: [safeTransactionData]_52})_52_52// Sign the transaction with the first owner_52const signedTx = await protocolKit.signTransaction(signMessageTx)_52_52// Connect the protocol kit to the second owner_52const protocolKitOfOwner2 = await protocolKit.connect({_52 signer: OWNER_2_PRIVATE_KEY_52})_52_52// Sign and execute the transaction as the second owner_52const transactionResult = await protocolKitOfOwner2.executeTransaction(_52 signedTx_52)_52_52// Check the transaction hash to see if the transaction settled_52console.log('transactionResult', transactionResult)
The protocolKit
is an instance of the Safe
class.
For more information on instantiating the Protocol Kit, refer to the Protocol Kit Quickstart section.
Initialize the Monerium client and authenticate the users
After the message is signed and the transaction is executed, the users can authenticate with Monerium.
_15import { MoneriumClient } from '@monerium/sdk'_15_15// Initialize the Monerium Client_15const monerium = new MoneriumClient({_15 clientId: 'a1b2c3-x7y8y9', // Get your client ID from Monerium_15 environment: 'sandbox' // Use the appropriate Monerium environment ('sandbox' | 'production')_15})_15_15// Start the Monerium authentication flow and send the users to Monerium_15await monerium.authorize({_15 address: safeAddress, // The address of the users' Safe_15 signature: '0x', // '0x' for Safe authentication lets Monerium look for the signature on-chain_15 redirectUrl: 'http://localhost:3000/return', // URL where Monerium will redirect the users after authenticating_15 chainId: 11155111 // Chain ID of Sepolia in this example_15})
Calling authorize
will redirect the users to the Monerium login page.
Authenticate with Monerium
At Monerium, the users need to log in or create an account.
Once logged in, Monerium will verify the ownership of the Safe by checking the signed message.
After successful verification, Monerium will create an IBAN and link it to the Safe.
The users will then be redirected to the specified redirectUrl
with the new session id as a GET parameter.
Finish the authentication
Once the users land back on your page, finish the authentication process.
_10// Returns true, if the users are authorized_10const isAuthorized = await monerium.getAccess()
Congratulations, you authenticated your users with Monerium and linked the Safe to the Monerium account.
Step 2: Place an Order
Once users are authenticated with Monerium, they can place an order to transfer tokens from their Safe to an IBAN.
Get some tokens
First, your users needs to obtain some EURe test tokens on Sepolia:
- Log into the Monerium Sandbox (opens in a new tab) account.
- Click on the
ADD MONEY
button. - Create a test IBAN transfer onto the account. The tokens from this test transfer will be available in the Safe.
Send an order to Monerium
To send tokens from a Safe to an IBAN, users must send the order to Monerium and sign a message with their account.
_30import { placeOrderMessage } from '@monerium/sdk'_30_30const amount = '10' // Specify the amount in Euro_30const iban = 'DK4878805291075472' // The target IBAN_30_30// 'Send EUR 10 to DK4878805291075472 at Fri, 17 May 2024 20:55:29Z'_30const orderMessage = placeOrderMessage(amount, 'eur', iban)_30_30// Send the order to the Monerium backend_30const order = await moneriumClient.placeOrder({_30 amount,_30 signature: '0x',_30 currency: 'eur',_30 address: safeAddress, // the Safe address_30 counterpart: {_30 identifier: {_30 standard: 'iban',_30 iban_30 },_30 details: {_30 firstName: 'User',_30 lastName: 'Userson',_30 county: 'AL'_30 }_30 },_30 message: orderMessage,_30 memo: 'Powered by Monerium SDK',_30 chain: 'ethereum',_30 network: 'sepolia'_30})
Monerium will listen for the sign message transaction of the following step on the selected chain and execute the order once the transaction settles.
Sign the order with the Safe
Now, the Safe needs to sign the order. To do this, send a sign message transaction to the blockchain. In practice, one owner might propose this transaction using the Safe Transaction Service. The other owner confirms the message and sends the transaction through the Safe{Wallet} (opens in a new tab) UI.
_26// Hash and encode the order message_26const txData = signMessageContract.encode('signMessage', [_26 hashSafeMessage(orderMessage)_26])_26_26// Assemble a transaction object_26const safeTransactionData = {_26 to: await signMessageContract.getAddress(),_26 value: '0',_26 data: txData,_26 operation: OperationType.DelegateCall_26}_26_26// Create a transaction with the Safe\{Core\} SDK_26const signMessageTx = await protocolKit.createTransaction({_26 transactions: [safeTransactionData]_26})_26_26// Sign the transaction with the first owner_26const signedTx = await protocolKit.signTransaction(signMessageTx)_26_26// Sign and execute the transaction with the second owner_26const transactionResult = await protocolKitOfOwner2.executeTransaction(signedTx)_26_26// Verify on-chain settlement_26console.log('transactionResult', transactionResult)
Monerium will execute the order once the transaction settles.
Well done! You have linked your users' Safes to an IBAN, bridging the gap between blockchain and traditional payment rails.
Further reading
- Monerium Developer Portal (opens in a new tab)
- Add event listeners (opens in a new tab) to make your app more interactive.