cosmiframe
Cosmiframe assists in establishing a Cosmos wallet connection through an iframe.
An outer (parent) window can act as a wallet for an iframe.
Installation
npm install @dao-dao/cosmiframe @cosmjs/amino @cosmjs/proto-signing
Security
It is very important to trust the outer app, since supporting this functionality
opens up the possibility for the outer app to manipulate messages before asking
the user to sign them.
At the end of the day, everything still needs to be approved by a wallet, so a
malicious app wrapping your app only has so much power, but relying on user
intelligence as a line of defense is definitely not a good idea, especially
because most users don't have the tools to validate that the smart contract
messages they're seeing are correct.
Cosmiframe enforces security by requiring you to specify allowed origins in the
constructor on client instantiation (seen in the examples below). Though it's
possible to workaround this, I urge you not to. I made it slightly harder than
just passing in '*'
in the constructor, which is the best I can do (and I'm
not telling you how 😡). Don't be lazy and risk your user's money.
All requests verify origin on message sending and receiving, which should
automatically prevent any messages being sent to or received from an origin you
haven't explicitly allowed. If you want to perform an additional security check
which also validates that Cosmiframe is in fact listening, you can use isReady
in the client.
import { Cosmiframe } from '@dao-dao/cosmiframe'
const client = new Cosmiframe([
'https://daodao.zone',
'https://dao.daodao.zone',
])
const readyAndSafeToUse = await client.isReady()
App integration
Integrating Cosmiframe to allow your app to be used inside iframes on another
app or apps is very straightforward. To retrieve an offline signer for use in a
signing client, which you would normally get from a wallet, get it from the
Cosmiframe
client instead:
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { Cosmiframe } from '@dao-dao/cosmiframe'
const signer = new Cosmiframe([
"https://daodao.zone",
"https://dao.daodao.zone",
]).getOfflineSigner("cosmoshub-4")
const client = await SigningCosmWasmClient.connectWithSigner(
'https://rpc...',
signer
)
await client.sendTokens(...)
await client.execute(...)
If you are used to interacting with Keplr's interface (e.g. maybe you've created
a wallet adapter system based on Keplr's functions), which is shared by many
wallets, you can retrieve a client that conforms to Keplr's interface and simply
proxies requests to the parent. Then, any parent that adds support for functions
defined in Keplr's interface should be supported:
import { Cosmiframe } from '@dao-dao/cosmiframe'
const keplr = new Cosmiframe([
"https://daodao.zone",
"https://dao.daodao.zone",
]).getKeplrClient()
await keplr.experimentalSuggestChain(...)
await keplr.enable('cosmoshub-4')
const key = await keplr.getKey('cosmoshub-4')
const signer = await keplr.getOfflineSignerOnlyAmino('cosmoshub-4')
const client = await SigningCosmWasmClient.connectWithSigner(
'https://rpc...',
signer
)
await client.sendTokens(...)
await client.execute(...)
Because wallet interfaces vary slightly depending on wallet and adapter library,
it's up to the parent to properly support and redirect requests. Some parents
may pass requests directly through to a connected wallet, and some may handle
message signature requests manually, wrapping them with other messages.
Example
This example allows an iframe to interact with Keplr connected to DAO DAO (this
is unrelated to the Keplr-like client support mentioned above). The first step
is performed in the parent app, and the second step is performed in the iframe
app.
- Begin listening from the parent window:
import { Cosmiframe } from '@dao-dao/cosmiframe'
const cosmiframe = new Cosmiframe([
'https://daodao.zone',
'https://dao.daodao.zone',
])
cosmiframe.listen({
iframe: document.getElementById('iframe'),
target: window.keplr,
getOfflineSignerDirect: window.keplr.getOfflineSigner.bind(window.keplr),
getOfflineSignerAmino: window.keplr.getOfflineSignerOnlyAmino.bind(
window.keplr
),
})
- Request method calls from the iframe:
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { Cosmiframe } from '@dao-dao/cosmiframe'
const cosmiframe = new Cosmiframe([
"https://daodao.zone",
"https://dao.daodao.zone",
])
await cosmiframe.p.enable('cosmoshub-4')
const directAndAminoSigner = cosmiframe.getOfflineSigner('cosmoshub-4')
const directSigner = cosmiframe.getOfflineSignerDirect('cosmoshub-4')
const aminoSigner = cosmiframe.getOfflineSignerAmino('cosmoshub-4')
const signingClient = await SigningCosmWasmClient.connectWithSigner(
'https://rpc...',
aminoSigner
)
const keplr = cosmiframe.getKeplrClient()
await keplr.experimentalSuggestChain(...)
await keplr.enable('cosmoshub-4')
const key = await keplr.getKey('cosmoshub-4')