A reference implementation for integrating Meso's on/off ramps into iOS applications.
For more details on the Meso integration, view the meso-js docs.
Meso does not have an official iOS SDK. However, this repo demonstrates the steps required to use Meso in an iOS application. Instead of rendering the Meso experience inside an iframe, this uses a WebView. The example helper library does two things:
postMessage
capabilities between the Meso window and your application. See Events
for more.📓 Currently, the SDK is in private beta. To request access, contact support@meso.network.
To use Meso, you must have a Meso partner
account. You can reach out to
support@meso.network to sign up. During the
onboarding process, you will need to specify the
origin of your dApp
or web application to ensure the Meso window operates within your application. Meso
will then provide you with a partnerId
for use with the SDK.
The demo application is built in Swift and uses SwiftUI. Using SwiftUI is not a requirement.
The logic for initializing and managing the Meso window lives in Meso.swift. This library is used inside of the main ContentView
.
To initialize Meso, you will need to configure the transfer (see reference for details).
// This is an example static configuration. Typically, this will by dynamically populated in your application at runtime.
let mesoTransferConfiguration = MesoTransferConfiguration(
partnerId: "<YOUR_PARTNER_ID>",
network: Network.solanaMainnet,
// This is just an example Solana address. You will need to input your own wallet.
walletAddress: walletAddress,
sourceAmount: transferAmount,
destinationAsset: Asset.sol,
environment: Environment.sandbox
)
let meso = Meso(configuration: mesoTransferConfiguration)
meso.on { event in
switch event {
case .configurationError(let payload):
print("[Configuration Error]: \(payload)")
case .unsupportedAssetError(let payload):
print("[Unsupported Asset Error]: \(payload)")
case .unsupportedNetworkError(let payload):
print("[Unsupported Network Error]: \(payload)")
case .requestSignedMessage(let payload, let callback):
// This demonstrates a generic method of signing messages with Solana wallets. In the real world, you would
// typically use a library or your own signing implementation.
// If the user cancels or rejects signing, or there is a failure, return nil – `callback(nil)`
callback(signMessage(messageToSign: payload.messageToSign))
case .close:
showMeso = false
case .transferApproved(let payload):
print("Handling `transferApproved` \(payload.transfer.id), \(payload.transfer.status)")
case .transferComplete(let payload):
let networkTransactionId = payload.transfer.networkTransactionId ?? "unknown"
print("Transfer complete! mesoId: \(payload.transfer.id), networkTransactionId: \(networkTransactionId)")
showMeso = false
default:
print("Unknown or discarded event")
}
}
You can then call meso.transfer()
when you want to render Meso in a WebView.
Example:
For example, in a SwiftUI View, you can do something like this:
let meso = Meso(...) // Your Meso configuration
meso.on { event in
// Handle events from Meso
}
struct ContentView: View {
@State private var showMeso = false
var body: some View {
ZStack {
VStack {
Button("Buy Crypto") {
if !showMeso {
showMeso.toggle()
}
}
}
if showMeso {
// Calling `meso.transfer` will render the WebView
meso.transfer()
}
}
}
}
You can close the Meso WebView at any time by calling meso.destroy()
.
For a detailed reference, view the meso-js
docs.
MesoTransferConfiguration
The MesoTransferConfiguration
struct is located in Meso.swift.
Property | Type | Description |
---|---|---|
partnerId |
String |
Unique ID for your partner account. (See Account setup) |
network |
Network |
The network to be used for the transfer. |
walletAddress |
String |
The wallet address for the user. This address must be compatible with the selected network and destinationAsset . |
sourceAmount |
Float |
A JSON-string-serializable amount for the Transfer. |
destinationAsset |
Asset |
The asset to be transferred. |
environment |
Environment |
The Meso environment to use. Typically you will use sandbox during development and production when you release your application. |
authenticationStrategy |
AuthenticationStrategy |
Determines the authentication mechanism for users to perform a transfer. In all scenarios, the user will still be required to perform two-factor authentication (2FA) and, in some cases provide email/password. If omitted, this will default to .walletVerification . |
Network
A CAIP-2 network identifier.
kind: enum
ethereumMainnet
: "eip155:1"
solanaMainnet
: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
polygonMainnet
: "eip155:137"
Asset
kind: enum
sol
: Solanaeth
: Ethereumusdc
: USDCEnvironment
kind: enum
Description | |
---|---|
sandbox |
In this environment, no crypto assets are transferred and no fiat assets are moved. |
production |
In this environment, production networks will be used to transfer real crypto assets. Fiat assets are moved |
AuthenticationStrategy
kind: enum
Description | |
---|---|
walletVerification |
Verify wallet by signing a message. New users and returning users with new wallets will still need to perform 2FA and login with email/password. |
headlessWalletVerification |
Verify a wallet by signing a message in the background without prompting the user. This is useful for scenarios such as embedded wallets. New users and returning users with new wallets will still need to perform login and 2FA. |
bypassWalletVerification |
Bypass wallet signing altogether and rely only on email/password and 2FA. This is useful for cases where pre-deployment smart contract wallets are being used and wallet verification cannot be performed. |
The meso
instance will dispatch events at various points in the lifecycle of a Transfer session.
You can handle these events like so:
meso.on { event in
switch event {
case .configurationError(let payload):
print("[Configuration Error]: \(payload)")
case .unsupportedAssetError(let payload):
print("[Unsupported Asset Error]: \(payload)")
case .unsupportedNetworkError(let payload):
print("[Unsupported Network Error]: \(payload)")
case .requestSignedMessage(let payload, let callback):
// This demonstrates a generic method of signing messages with Solana wallets. In the real world, you would
// typically use a library or your own signing implementation.
// If the user cancels or rejects signing, or there is a failure, return nil – `callback(nil)`
callback(signMessage(messageToSign: payload.messageToSign))
case .close:
showMeso = false
case .transferApproved(let payload):
print("Handling `transferApproved` \(payload.transfer.id), \(payload.transfer.status)")
case .transferComplete(let payload):
let networkTransactionId = payload.transfer.networkTransactionId ?? "unknown"
print("Transfer complete! mesoId: \(payload.transfer.id), networkTransactionId: \(networkTransactionId)")
showMeso = false
default:
print("Unknown or discarded event")
}
}
Each event
is a MesoEvent
and will provide one of the following payloads:
Description | |
---|---|
transferApproved(payload: TransferPayload) |
The Transfer has been approved and will have a status of TransferStatus.approved |
transferComplete(payload: TransferPayload) |
The Transfer is complete, funds have moved, and will have a status of TransferStatus.complete |
error(payload: ErrorPayload) |
An error occurred in the application. A client-friendly error will be surfaced. |
configurationError(payload: ErrorPayload) |
The configuration is malformed and values may need to be updated. See ErrorPayload |
unsupportedNetworkError(payload: ErrorPayload) |
The provided network is not currently supported by Meso. See ErrorPayload |
unsupportedAssetError(payload: ErrorPayload) |
The provided asset is not currently supported by Meso. See ErrorPayload |
requestSignedMessage(payload: RequestSignedMessagePayload, callback: (_ signedMessage: String?) -> Void) |
The Meso window has requested a message to be signed to prove ownership of a wallet. Upon signing, this callback can be used to return the signed message. If no String is returned in the callback, it is assumed the user canceled or rejected the message signing or there was an error. |
close |
The user has manually opted to close the Meso window. |
TransferPayload
A wrapped result of a transfer status update to be sent to the partner application.
| Properties | Description |
| transfer
| Transfer |
Transfer
Details of a Meso transfer.
Property | Type | Description |
---|---|---|
id |
String |
The Meso id for this transfer . |
status |
TransferStatus |
The status of the transfer . |
updatedAt |
String |
An ISO-8601 date string. |
networkTransactionId |
String? |
The on-chain identifier for the transfer. Note: This will only be available for transfers that are complete . |
TransferStatus
kind: enum
Description | |
---|---|
approved |
The transfer has been approved and is pending completion. At this point, funds have not yet been moved. |
complete |
The transfer is complete and the user's funds are available. |
declined |
The transfer has failed. |
executing |
The transfer is in flight. |
ErrorPayload
Type | Description | |
---|---|---|
message |
String |
A client-friendly error message. |
RequestSignedMessagePayload
The payload received when the webView
prompts for a wallet signature.
Type | Description | |
---|---|---|
messageToSign |
String |
An opaque message to be signed via an action in the Meso window. |
In sandbox, you can use the following values for testing:
transfer
configuration
sourceAmount
"666.66"
will cause onboarding to fail due to risk checks and the user
will be frozen"666.06"
will fail the transfer with the payment being declined000000
will succeed5305484748800098
435
12/2025
0000
will require the user enter a fully valid SSN (you can use 123345432
).While the example application targets iOS 15+, Meso should work with iOS 13-14.