@lukso/up-provider
v0.2.1
Published
The up-provider allows dApps to function as mini-apps and allows parent applications to one-click-connect to your mini-app using the up-provider.
Downloads
438
Readme
up-provider
The up-provider allows dApps to function as mini-apps and allows parent applications to one-click-connect to your mini-app using the up-provider.
Mini-apps are dApps loaded in iframes in the context of other applications.
This package also contains the server connector for parent applications (See on the end)
Installation
npm install @lukso/up-provider
Grid Widget side of provider
Client side (for example inside of a grid widget) setup
Using with viem
import { createClientUPProvider } from '@lukso/up-provider'
import { createWalletClient, createPublicClient, custom } from 'viem'
import { lukso } from 'viem/chains'
// Construct the up-provider
const provider = createClientUPProvider()
// Create public client if you need direct connection to RPC
const publicClient = createPublicClient({
chain: lukso,
transport: http(),
})
// Create wallet client to connect to provider
const walletClient = createWalletClient({
chain: lukso,
transport: custom(provider),
})
Using with web3.js
import { createClientUPProvider } from '@lukso/up-provider';
import Web3, { type EthExecutionAPI, type SupportedProviders } from 'web3';
// Create the up-provider
const provider = createClientUPProvider();
// Wrap provider into web3 for usage.
const web3 = new Web3(provider as SupportedProviders<EthExecutionAPI>);
Using ethers.js
import { createClientUPProvider } from '@lukso/up-provider'
import { type Eip1193Provider, ethers } from 'ethers'
// Create the up-provider
const provider = createClientUPProvider()
// Wrap provider into ethers for usage.
const browserProvider = new ethers.BrowserProvider(upProvider as unknown as Eip1193Provider)
Accounts and Chains
The up-provider gives your the chain and accounts that the parent page's supports.
Accounts
As the mini-app can NOT force eth_requestAccounts
, you need to listen to the accountsChanged
event
to see when a user of the parent app chose to connect to your mini-app. You can then call eth_accounts
to get a list of accounts that can execute transactions.
provider.on('accountsChanged', (_accounts: `0x${string}`[]) => {
// Update your interface to show that the user is connected and enable your transaction buttons
})
// Returns a list of allowed accounts
provider.allowedAccounts
//> ['0x1234...']
Context accounts
A parent page can also provide you with a context account. This are one or more accounts,
that are relevant for the context in which the mini-app was loaded.
On universaleverything.io this is the universal profile that has the mini-app in its grid,
while account[0]
will be the visitor that connected to your mini-app.
// Event for context accounts change
provider.on('contextAccountsChanged', (contextAccountsArray: `0x${string}`[]) => {
// Do something with the context contextAccountsArray[0]
})
// Returns a list of context accounts, thet the parent app wants to provide
provider.contextAccounts
//> ['0x1234...']
Examples to monitor "accountsChanged" and "chainChained"
You should use some kind of watch/useEffect or other reactive function to watch the
accountsChanged
, contextAccountsChanged
and chainChanged'
events. You can initially call eth_accounts
, up_contextAccounts
(or provider.contextAccounts) and eth_chainId
to initialize accounts and chainId.
Example React code to monitor accountsChanged, contextAccountsChanged and chainChained
The output is a boolean walletConnected which will be true/false.
const [chainId, setChainId] = useState<number>(0)
const [accounts, setAccounts] = useState<Array<`0x${string}`>>([])
const [contextAccounts, setContextAccounts] = useState<Array<`0x${string}`>>([])
const [walletConnected, setWalletConnected] = useState(false)
const [amount, setAmount] = useState<number>(minAmount)
const [error, setError] = useState('')
const validateAmount = useCallback((value: number) => {
if (value < minAmount) {
setError(`Amount must be at least ${minAmount} LYX.`)
} else if (value > maxAmount) {
setError(`Amount cannot exceed ${maxAmount} LYX.`)
} else {
setError('')
}
setAmount(value)
}, [])
useEffect(() => {
validateAmount(amount)
}, [amount, validateAmount])
const updateConnected = useCallback((accounts: Array<`0x${string}`>, contextAccounts: Array<`0x${string}`>, chainId: number) => {
console.log(accounts, chainId)
setWalletConnected(accounts.length > 0 && contextAccounts.length > 0 && chainId === 42)
}, [])
// Monitor accountsChanged and chainChained events
// This is how a grid widget gets it's accounts and chainId.
// Don't call eth_requestAccounts() directly to connect,
// The connection will be injected by the grid parent page.
useEffect(() => {
async function init() {
try {
const _chainId: number = (await web3.eth.getChainId(dataType)) as number
setChainId(_chainId)
const _accounts = (await web3.eth.getAccounts()) as Array<`0x${string}`>
setAccounts(_accounts)
const _contextAccounts = provider.contextAccounts
updateConnected(_accounts, _contextAccounts, _chainId)
} catch (error) {
// Ignore error
}
}
init()
const accountsChanged = (_accounts: Array<`0x${string}`>) => {
setAccounts(_accounts)
updateConnected(_accounts, contextAccounts, chainId)
}
const contextAccountsChanged = (_accounts: Array<`0x${string}`>) => {
setContextAccounts(_accounts)
updateConnected(accounts, _accounts, chainId)
}
const chainChanged = (_chainId: number) => {
setChainId(_chainId)
updateConnected(accounts, contextAccounts, _chainId)
}
provider.on('accountsChanged', accountsChanged)
provider.on('chainChanged', chainChanged)
provider.on('contextAccountsChanged', contextAccountsChanged)
return () => {
provider.removeListener('accountsChanged', accountsChanged)
provider.removeListener('contextAccountsChanged', contextAccountsChanged)
provider.removeListener('chainChanged', chainChanged)
}
}, [chainId, accounts[0], contextAccounts[0], updateConnected])
Example Vue code to monitor accountsChanged and chainChanged
const chainId = ref<number | null>(null)
const accounts = ref<string[]>([])
const contextAccounts = ref<string[]>([])
const walletConnected = ref<boolean>(false)
// Allocate the client up provider.
const provider = createClientUPProvider()
const web3 = new Web3(provider as SupportedProviders<EthExecutionAPI>)
// Initially retrieve chainId and accounts
web3.eth
?.getChainId()
.then(_chainId => {
chainId.value = Number(_chainId)
walletConnected.value = accounts.value.length > 0 && contextAccounts.value.length > 0 && chainId.value === 42
})
.catch(error => {
// Ignore error
})
web3.eth
?.getAccounts()
.then(_accounts => {
accounts.value = _accounts || []
})
.catch(error => {
// Ignore error
})
provider
.request('up_contextAccounts', [])
.then(_accounts => {
contextAccounts.value = _accounts || []
})
.catch(error => {
// Ignore error
})
// Watch for changes in accounts
provider.on('accountsChanged', (_accounts: `0x${string}`[]) => {
accounts.value = [..._accounts]
})
// Watch for changes in contextAccounts
provider.on('contextAccountsChanged', (_accounts: `0x${string}`[]) => {
contextAccounts.value = [..._accounts]
})
// Watch for changes in chainId
provider.on('chainChanged', (_chainId: number) => {
chainId.value = _chainId
})
watch(
() => [chainId.value, accounts.value, contextAccounts.value] as [number, Array<`0x${string}`>, Array<`0x${string}`>],
([chainId, accounts, contextAccounts]: [number, Array<`0x${string}`>, Array<`0x${string}`>]) => {
// Optionally you can do additional checks here.
// For example if you check for accounts?.[0] !== accounts?.[1] you can
// ensure that the connected account is not the page owner.
// The button will be disabled if the walletConnected flag is false.
walletConnected.value = accounts?.length > 0 && contextAccounts?.length > 0 && chainId === 42
}
)
Parent page side of the up-provider
The parent page side of the up-provider, is used in pages that host mini-apps and can pass up connections from iframes to a parent provider like window.ethereum
(referred to as orignalProvider below)
import { UPClientChannel, createUPProviderConnector } from '@lukso/up-provider'
// Pass in the provider you want the page to use.
const providerConnector = createUPProviderConnector(originalProvider, ['https://rpc.mainnet.lukso.network'])
// or later on call
// globalProvider.setupProvider(originalProvider, ['https://rpc.mainnet.lukso.network'])
providerConnector.on('channelCreated', ({ channel, id }) => {
// Called when an iframe connects.
// then channel can control the connection.
// Usually you would store this in a ref and use it within a dialog to control the connection.
// for example
channel.enabled = true;
// The addresses and chainId it will cause addressChanged and chainChanged events on the client provider.
channel.setAllowedAccounts([profileAddress, ...extraAddresses])
})
Additonal parent page provider functions
// These setting will override what's returned from the parent provider
providerConnector.setAllowedAccounts(addressArray)
providerConnector.setChainId(chainId)
providerConnector.setContextAccounts(addressArray)
// These settings will override values specifically for one connection
channel.setAllowedAccounts(addressArray)
channel.setChainId(chainId)
channel.setContextAccounts(addressArray)
// All setX functions can also be used as a writable property.
channel.enable = true
channel.accounts = addressArray
channel.chainId = 42
channel.contextAccounts = addressArray
// There is also a utility function called setupChannel which can supply
// all enable, accounts, contextAccounts and chainId at the same time
channel.setupChannel(enable, [profileAddress], [contextAddress], chainId)