Esse artigo foi escrito por: Lak Baklanov e traduzido por Dimitris Calixto, artigo original disponível aqui
Construa seu próprio dApp para iOS
A resposta curta para essa pergunta é sim, é possível.
Neste artigo, vou mostrar para você como implementei com os meus colegas da Custom App, lançamos a aplicação W3dding (atualmente para iPhone X e posteriormente suportada), o que tivemos que passar e como fazer o seu próprio dApp móvel nativo para iOS. Se você mal pode esperar para fazer o seu próprio dApp mobile, aqui estão os links para a biblioteca open-source de conexão de carteira modificada por nós e o aplicativo de exemplo.
Este artigo falará sobretudo do desenvolvimento do dApp especificamente na plataforma iOS. Para melhor compreensão, você deve ser familiarizado com o Swift 5 e ter conhecimentos gerais sobre a blockchain.
Introdução
Há alguns meses atrás, um dia, o meu chefe, que tem um contexto de blockchain bastante amplo, me perguntou: "Lev, parece ter algumas conversas sobre aplicativos web3 mobile, você já ouviu alguma coisa sobre isso?". Depois disso, fui investigar essa questão, tendo apenas uma compreensão geral de como funciona o mundo crypto (antes disso, os programadores mobile não participavam nos nossos projetos crypto).
Claro que, após um pouco de procura de várias soluções, consegui encontrar numerosas bibliotecas Swift que trabalham com a web3. No entanto, o envio direto de transações requer uma chave privada, que nenhum usuário no seu perfeito juízo forneceria a um aplicativo de terceiros. Portanto, utilizar apenas essas bibliotecas web3 pode ser adequado para escrever seu próprio aplicativo de carteira, mas não para implementar uma dApp.
Também aprendi sobre a possibilidade de utilizar navegadores embutidos em aplicações de carteira, mas isso não me satisfez, pois estava interessado em aprender sobre a possibilidade de implementar um aplicativo mobile nativo.
Além disso, foi decidido procurar quaisquer exemplos de dApp com a funcionalidade específica da que necessitamos. Procurei muitas grandes aplicações (nft marketplaces, exchanges, etc.), mas inicialmente não consegui encontrar um único aplicativo mobile nativo qual fosse possível enviar transações através de uma carteira. Havia aplicativos em que só se podia ligar à carteira (por exemplo, OpenSea). Parecia ser um absurdo que no maior aplicativo mobile do mundo de mercado de NFT fosse impossível fazer transações e comprar diretamente NFT. Pensei - talvez, simplesmente não seja possível?
Sumário
1 . Introdução
2 . Wallet Connect
3 . Atualizações da biblioteca Wallet Connect
..... . Ícone da carteira feito opcional
..... . Acrescentando a codificação de porcentagem em falta
..... . Desconectando a ligação de retorno quando desconectado do lado da carteira
..... . Acrescentada a chamada de retorno didSubscribe
..... . Adicionada bandeira de reconexão para desligar a chamada de retorno
..... . Adicionado id para pedido de desconexão
..... . Adicionado 1 segundo de tempo limite de reconexão
4 . Observações úteis da Web3 mobile
5 . Links
Wallet Connect
Como descobri mais tarde, a maior parte dos aplicativos de navegação em blockchain utilizam a tecnologia Wallet Connect para se ligar a carteiras e enviar transações. Wallet Connect é o protocolo pelo qual o dApp e a carteira interagem. No processo de interação, o dApp e a carteira utilizam uma ponte intermediária. Para criar uma nova sessão, o dApp envia um pedido à ponte selecionada, após o qual é necessária a confirmação da ligação a partir do lado da carteira. Após criar uma sessão, a aplicação pode enviar pedidos de transações, que terão de ser confirmados na carteira. Num browser, esta ligação funciona da seguinte forma: a aplicação web envia um pedido para criar uma sessão e gera um código QR com a informação necessária para ligar (bridge url, session id, etc.), e o aplicativo da carteira digitaliza este código.
O site Wallet Connect tem uma grande quantidade de documentação e exemplos de trabalho com o protocolo na construção de aplicações web, mas essa informação não está disponível para aplicativos mobile nativos. Existe apenas uma seção Mobile Linking que diz que a Wallet Connect pode funcionar em aplicações móveis utilizando ligações profundas. Ao criar uma sessão, a aplicação envia um pedido ao servidor da ponte e direciona o usuário para a carteira selecionada através de uma ligação profunda, onde ele confirma a criação da sessão.
A seguir, encontrei a biblioteca oficial Swift Wallet Connect e um exemplo da sua utilização. Parece que todas as peças estejam reunidas e seja possível você criar a sua própria dApp mobile nativa. No entanto, depois de ter executado o exemplo e brincado com ele durante algum tempo, percebi que não estava funcionando como esperado. Problemas que foram encontrados enquanto trabalhava com o exemplo:
- Não foi possível conectar em algumas carteiras - algumas carteiras simplesmente não reagiram depois de terem sido lançadas através de uma ligação profunda, e algumas outras colidiram com a aplicação após a ligação (claro que estou falando de carteiras que suportam a Wallet Connect).
- Se a carteira for conectada com sucesso, então a sessão não foi gerida corretamente. Pode ser facilmente perdida do lado do dApp, enquanto é exibida como ativa na aplicação da carteira.
- Se a carteira for conectada com sucesso, então o envio direto da transação poderá não funcionar.
Em geral, o exemplo não funcionou na sua forma atual. Depois disso, comecei a procurar quaisquer exemplos ou artigos sobre a construção de um dApp mobile. Como antes, vi muitos guias sobre como ter sucesso ao implementar uma aplicação web, mas não havia materiais relacionados a aplicações mobiles :(
Analisei a atividade nos repositórios acima referidos - os últimos commits foram há vários meses, um grande número de problemas em aberto sem resposta. Pesquisei os problemas e vi vários em que os próprios usuários da biblioteca resolviam os problemas encontrados, sem a ajuda dos autores. Depois descobri que neste momento os criadores estão concentrados no Wallet Connect v2 (atualmente em beta), e a primeira versão foi deixada sem atenção. Assim, percebi que a única forma de utilizar o Wallet Connect e ter uma funcionalidade adequada era fazer um fork da biblioteca e tentar resolver os problemas.
Atualizações da biblioteca Wallet Connect
Antes de fazer alterações, a biblioteca foi testada utilizando várias carteiras que suportam Wallet Connect e a blockchain Polygon: Trust Wallet, MetaMask, TokenPocket, SafePal, Unstoppable Wallet, AlphaWallet, e MathWallet. A seguir descreve brevemente as alterações feitas na biblioteca Wallet Connect. Não serão descritas aqui pequenas correções, tais como a implementação do protocolo identificável pela estrutura da Sessão. Se desejado, todos os commits podem ser visualizados no repositório. Alterações efetuadas:
Ícone da carteira feito opcional
Vamos olhar a estrutura ClientMeta
:
public struct ClientMeta: Codable, Equatable {
public let name: String
public let description: String?
public let icons: [URL]
public let url: URL
public let scheme: String?
}
DAppInfo
e WalletInfo
contém ClientMetaI
e quando recebemos WalletInfo
JSON sem um campo icons
a aplicação irá colidir. Foi o caso da ligação com algumas carteiras (por exemplo, Safepal).
Acrescentando a codificação de porcentagem em falta
Na biblioteca original, havia apenas uma codificação percentual do campo da ponte, enquanto que o protocolo requer uma ligação profunda totalmente codificada em porcentagem. Metamask pode tratar deste caso e codificá-lo para si, mas não será capaz de se ligar a outras carteiras.
Desconectando a ligação de retorno quando desconectado do lado da carteira
Na maioria das aplicações de carteira, os usuários podem desligar as sessões. Portanto, se não houver tratamento deste caso a sua aplicação pensará que tem uma sessão ativa, mas isso não é verdade.
Acrescentada a chamada de retorno didSubscribe
Quando você quer conectar a uma carteira, é preciso primeiro conectar com a ponte e enviar um novo pedido de sessão. Quando um pedido é enviado, está pronto para abrir uma ligação profunda e solicitar ao usuário que se ligue ao aplicativo da carteira. Na biblioteca original, houve um atraso constante antes de se abrir a ligação profunda. Assim, se o seu pedido de sessão não foi enviado por qualquer razão, não verá qualquer mensagem no aplicativo da carteira.
Adicionada bandeira de reconexão para desligar a chamada de retorno
Antes de adicionar essa bandeira, ao receber uma chamada de retorno de desconexão, não havia maneira de compreender se a desconexão foi iniciada pela carteira/usuário ou se a ligação foi perdida por outras razões (internet ruim) e se estão em curso tentativas de reconexão.
Adicionado id para pedido de desconexão
Quando se desconecta de algumas carteiras (por exemplo, Trust Wallet), falta um campo de id necessário.
Adicionado 1 segundo de tempo limite de reconexão
A biblioteca Wallet Connect utiliza a biblioteca Starscream para gerir as ligações de websocket. E quando um usuário se desconecta por qualquer razão (por exemplo, má conexão à Internet), começa a tentar restabelecer a ligação muito frequentemente, o que não é bom para os recursos do dispositivo.
Exemplo de uso
Agora passaremos a um nível mais técnico. Nesta grande seção, vamos analisar a utilização da biblioteca com um exemplo e construir uma dApp autônoma. A aplicação será escrita utilizando a arquitetura SwiftUI e MVVM. Os pontos principais da criação do aplicativo serão considerados, mas algumas partes (por exemplo, o componente View e algumas funções auxiliares) serão omitidas. O código completo da aplicação pode ser visualizado no repositório.
O primeiro passo é instalar uma versão modificada da biblioteca da Wallet Connect. Pode ser feita utilizando o Gestor de Pacotes Swift:
- Usando o Xcode 13 vá para Arquivo > Adicionar pacotes > Clique na barra de pesquisa no canto superior direito
- Cole a URL do projeto: https://github.com/penachett/WalletConnectSwift
- Clique em próximo e selecione o alvo do projeto
A nossa aplicação incluirá toda a funcionalidade básica de trabalhar com a Wallet Connect:
- Capacidade de conectar a uma das carteiras (Trust Wallet ou Metamask);
- Capacidade de enviar uma transação;
- Capacidade de desconectar;
- Tratar de todos os eventos de atualização de sessões.
Comecemos pela estrutura da nossa Wallet
.
struct Wallet: Hashable {
let name: String
let mainUrl: String
let appStoreLink: String
let universalScheme: String
let nativeScheme: String
let linkForOpenOnly: String
func formWcDeepLink(connectionUrl: String) -> String {
formEmptyDeepLink() + "wc?uri=\(connectionUrl)"
}
func formEmptyDeepLink() -> String {
if !universalScheme.isEmpty {
return "\(universalScheme)/"
} else {
return "\(nativeScheme)://"
}
}
func formLinkForOpen() -> String {
return linkForOpenOnly.isEmpty ? formEmptyDeepLink() : linkForOpenOnly
}
}
Aqui você vê alguns campos comuns como nome, url da carteira principal, e url da App Store.
Temos também 2 tipos de esquemas de ligação profunda - universal e nativa. Enquanto a Apple recomenda a utilização de esquemas universais, algumas carteiras têm apenas implementação de esquemas nativos. Além disso, os esquemas nativos de ligação profunda são úteis para verificar a disponibilidade da aplicação da carteira no dispositivo do usuário.
Os esquemas de ligação profunda podem ser difíceis de encontrar, e nem todos os criadores os mencionam explicitamente na sua documentação.
Esquemas de ligação profunda podem ser encontrados no repositório de um dos projetos de código aberto da Gnosis Safe. Também será necessário dirigir o usuário para a aplicação de carteira não só quando for necessário criar uma ligação, mas também quando uma transação precisa ser confirmada.
A função linkForOpenOnly
é necessária para este fim porque algumas carteiras (Carteira de Confiança no nosso exemplo) mostram um alerta de erro que a ligação profunda não pode ser analisada e confunde o usuário. Neste exemplo, forneci um link que abre a Polygon na Trust Wallet.
struct Wallets {
static let TrustWallet = Wallet(
name: "Trust Wallet",
mainUrl: "https://trustwallet.com",
appStoreLink: "https://apps.apple.com/app/apple-store/id1288339409",
universalScheme: "https://link.trustwallet.com",
nativeScheme: "trust",
linkForOpenOnly: "https://link.trustwallet.com/open_coin?asset=c966"
)
static let Metamask = Wallet(
name: "MetaMask",
mainUrl: "https://metamask.io",
appStoreLink: "https://apps.apple.com/app/metamask/id1438144202",
universalScheme: "https://metamask.app.link",
nativeScheme: "metamask",
linkForOpenOnly: ""
)
static let All = [TrustWallet, Metamask]
static func available() -> [Wallet] {
var res: [Wallet] = []
for wallet in All {
let nativeLink = "\(wallet.nativeScheme)://"
if let url = URL(string: nativeLink), UIApplication.shared.canOpenURL(url) {
res.append(wallet)
}
}
return res
}
static func bySession(session: Session?) -> Wallet? {
guard let session = session else { return nil }
let name = session.walletInfo?.peerMeta.name
let url = session.walletInfo?.peerMeta.url
if let name = name, let wallet = All.first(where: { $0.name == name }) {
return wallet
}
if let url = url?.absoluteString, let wallet = All.first(where: { $0.mainUrl == url } ) {
return wallet
}
return nil
}
}
A seguir, passamos à consideração da nossa lógica, teremos ela representada por duas classes: WalletConnect
e ExampleViewModel
.
Na classe WalletConnect
, acessaremos diretamente à biblioteca e passaremos as chamadas de retorno ao ViewModel
utilizando um delegado que implementaremos.
Consideremos alguns dos métodos do WalletConnect
que não estão relacionados com as chamadas de retorno da biblioteca.
func connect() -> String {
let wcUrl = WCURL(topic: UUID().uuidString,
bridgeURL: URL(string: "https://safe-walletconnect.gnosis.io/")!,
key: randomKey())
let clientMeta = Session.ClientMeta(name: "Test app",
description: "Wallet connect test app",
icons: [],
url: URL(string: "https://medium.com")!)
let dAppInfo = Session.DAppInfo(peerId: UUID().uuidString,
peerMeta: clientMeta,
chainId: 137) // Polygon chain
client = Client(delegate: self, dAppInfo: dAppInfo)
try! client.connect(to: wcUrl)
return wcUrl.fullyPercentEncodedStr
}
func reconnectIfNeeded() {
if let sessionObject = UserDefaults.standard.object(forKey: WalletConnect.sessionKey) as? Data,
let session = try? JSONDecoder().decode(Session.self, from: sessionObject) {
client = Client(delegate: self, dAppInfo: session.dAppInfo)
try? client.reconnect(to: session)
}
}
Aqui no método de ligação estamos construindo uma wallet connect URL, conectando ao servidor da ponte, enviando um pedido para criar uma sessão, e devolvendo a url para a colocar mais tarde numa ligação profunda.
Note que aqui estamos utilizando uma ponte pública https://safe-walletconnect.gnosis.io, há também outra ponte pública oficial - https://bridge.walletconnect.org. Qualquer ponte pode por vezes cair e não será possível utilizar a Wallet Connect.
Para grandes projetos comerciais, recomendo fazer a sua própria ponte (como mantê-la privada é uma boa questão num mundo web3 descentralizado:) ). Também útil é que se possa especificar a chainId
a que o usuário deve conectar.
Mas para a MetaMask não faz nada - o usuário se conecta à rede previamente escolhida e precisa mudar. A Trust Wallet utiliza a chainId
por padrão, mas o usuário pode mudar a rede de qualquer forma. Portanto, é necessário controlar sempre o que será chainId
no objeto da Session
após a ligação.
Lembre-se também que a Wallet Connect v1 apenas suporta blockchains compatíveis com EVM. O método seguinte reconnectIfNeeded
ressuscita a nossa antiga sessão se a tivermos guardada em UserDefaults. Será chamada em todas as inicializações de aplicações.
Agora vamos passar para as ligações de retorno da Wallet Connect
protocol WalletConnectDelegate {
func failedToConnect()
func didConnect()
func didUpdate(session: Session)
func didDisconnect(isReconnecting: Bool)
func didSubscribe(url: WCURL)
}
extension WalletConnect: ClientDelegate {
func client(_ client: Client, didFailToConnect url: WCURL) {
delegate.failedToConnect()
}
func client(_ client: Client, didConnect url: WCURL) { }
func client(_ client: Client, didSubscribe url: WCURL) {
delegate.didSubscribe(url: url)
}
func client(_ client: Client, didConnect session: Session) {
self.session = session
let sessionData = try! JSONEncoder().encode(session)
UserDefaults.standard.set(sessionData, forKey: WalletConnect.sessionKey)
delegate.didConnect()
}
func client(_ client: Client, didDisconnect session: Session, isReconnecting: Bool) {
if !isReconnecting {
UserDefaults.standard.removeObject(forKey: WalletConnect.sessionKey)
}
delegate.didDisconnect(isReconnecting: isReconnecting)
}
func client(_ client: Client, didUpdate session: Session) {
delegate.didUpdate(session: session)
}
}
Aqui, por exemplo, definimos a nossa WalletConnectDelegate
(que é feita de ClientDelegate
original, excluindo algumas informações) para chamar o nosso ViewModel que irá modificar o estado.
Guardamos a Session
para UserDefaults se nos conectarmos com sucesso e fazemos o oposto se estiver desconectada. Ignoramos didConnect
com URL porque aqui não estamos interessados no momento em que nos conectamos à ponte.
Vamos verificar a implementação deste delegado no nosso ViewModel.
extension ExampleViewModel: WalletConnectDelegate {
func failedToConnect() {
DispatchQueue.main.async { [unowned self] in
withAnimation {
isConnecting = false
isReconnecting = false
}
}
}
func didConnect() {
DispatchQueue.main.async { [unowned self] in
withAnimation {
isConnecting = false
isReconnecting = false
session = walletConnect?.session
if currentWallet == nil {
currentWallet = Wallets.bySession(session: session)
}
// Load initial web3 info here
}
}
}
func didSubscribe(url: WCURL) {
triggerPendingDeepLink()
}
func didUpdate(session: Session) {
var accountChanged = false
if let curSession = self.session,
let curInfo = curSession.walletInfo,
let info = session.walletInfo,
let curAddress = curInfo.accounts.first,
let address = info.accounts.first,
curAddress != address || curInfo.chainId != info.chainId {
accountChanged = true
do {
let sessionData = try JSONEncoder().encode(session)
UserDefaults.standard.set(sessionData, forKey: WalletConnect.sessionKey)
} catch {
print("Error saving session in update: \(error)")
}
}
DispatchQueue.main.async { [unowned self] in
withAnimation {
self.session = session
}
if accountChanged {
// Handle address change
}
}
}
func didDisconnect(isReconnecting: Bool) {
if !isReconnecting {
DispatchQueue.main.async { [unowned self] in
withAnimation {
isConnecting = false
session = nil
}
}
}
DispatchQueue.main.async { [unowned self] in
withAnimation {
self.isReconnecting = isReconnecting
}
}
}
}
A parte interessante aqui é a chamada de retorno do didSubscribe
. Assim, quando nós tivermos ligado à ponte e enviado um pedido de inicialização de sessão, estamos prontos para abrir uma ligação profunda e solicitar ao usuário a aplicação de carteira. Vamos seguir em frente.
func openWallet() {
if let wallet = currentWallet {
if let url = URL(string: wallet.formLinkForOpen()),
UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
}
func initWalletConnect() {
print("init wallet connect: \(walletConnect == nil)")
if walletConnect == nil {
walletConnect = WalletConnect(delegate: self)
if walletConnect!.haveOldSession() {
withAnimation {
isConnecting = true
}
walletConnect!.reconnectIfNeeded()
}
}
}
func connect(wallet: Wallet) {
guard let walletConnect = walletConnect else { return }
let connectionUrl = walletConnect.getConnectUrl()
pendingDeepLink = wallet.formWcDeepLink(connectionUrl: connectionUrl)
currentWallet = wallet
}
func disconnect() {
guard let session = session, let walletConnect = walletConnect else { return }
try? walletConnect.client?.disconnect(from: session)
withAnimation {
self.session = nil
}
UserDefaults.standard.removeObject(forKey: WalletConnect.sessionKey)
}
func triggerPendingDeepLink() {
guard let deepLink = pendingDeepLink else { return }
pendingDeepLink = nil
DispatchQueue.main.asyncAfter(deadline: .now() + deepLinkDelay) {
if let url = URL(string: deepLink), UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
// Abre o app na App Store ou faz algo mais
}
}
}
Aqui estão os métodos gerais ViewModel. connect
/ disconnect
que podem ser chamados a partir da UI, initWalletConnect
que é chamado sempre que esta aplicação é iniciada, openWallet
que simplesmente abre a carteira (para enviar uma transação), e o já mencionado triggerPendingDeepLink
. O nosso último bloco de código é a lógica de envio da transação:
func sendTx(to: String) {
guard let session = session,
let client = walletConnect?.client,
let from = walletAccount else {
print("nil client or session")
return
}
let tx = Client.Transaction(
from: from,
to: to,
data: "",
gas: nil,
gasPrice: nil,
value: "0x1",
nonce: nil,
type: nil,
accessList: nil,
chainId: nil,
maxPriorityFeePerGas: nil,
maxFeePerGas: nil)
do {
try client.eth_sendTransaction(url: session.url,
transaction: tx) { [weak self] response in
self?.handleResponse(response)
}
DispatchQueue.main.async {
self.openWallet()
}
} catch {
print("error sending tx: \(error)")
}
}
private func handleResponse(_ response: Response) {
DispatchQueue.main.async {
if let error = response.error {
print("got error sending tx: \(error)")
return
}
do {
let result = try response.result(as: String.self)
print("got response result: \(result)")
} catch {
print("Unexpected response type error: \(error)")
}
}
}
Tenha cuidado ao fixar o gasPrice
nas suas transações. As nossas carteiras (Trust Wallet e MetaMask) irão calculá-lo sem nós, mas, por exemplo, a Safepal irá lançar um erro desconhecido se não tiver definido o gasPrice
.
É também uma boa prática ir buscar o saldo do usuário antes de enviar uma transação, já que algumas carteiras simplesmente lhe devolverão um erro desconhecido ao tentar enviar uma transação para a qual não há saldo suficiente. Sim, as coisas não são tão fáceis neste jovem mundo mobile da web3 :) .
Observações úteis da Web3 mobile
- A Apple não compreende as aplicações web3. Você deverá estar preparado para que as suas submissões à App Store sejam rejeitadas várias vezes e terá de explicar algumas coisas aos revisores da Apple. No início, tivemos que provar que a nossa aplicação não é uma carteira (tínhamos um separador "Carteira" com a funcionalidade de ligar a carteira). Mais tarde, ao enviar uma das atualizações, o problema foi que utilizamos a autorização através de aplicações (carteiras) de terceiros. Você pode sempre tentar explicar a essência para eles em resposta, mas em caso de falha, terá que fazer algumas alterações.
- Você deve sempre testar com todas as carteiras que você dá suporte, uma vez que muitas vezes têm um comportamento bastante individual.
- Quando enviar uma transacção e abrir uma ligação profunda, a sua aplicação terá alguns segundos antes de passar para segundo plano. Portanto, se o usuário não confirmar a transação rapidamente, então não poderá receber uma resposta sobre o envio da transação. Para resolver este problema, pode utilizar tarefas de fundo, que darão um mínimo adicional de 30 segundos à vida de uma aplicação com uma ligação websocket ativa.
- Existem várias "características" quando se trabalha com a MetaMask que você precisa saber:
- Mesmo quando a transacção foi enviada com sucesso, o usuário pode receber uma notificação push de que algo correu mal. Isto pode confundir o usuário, então seria bom alertá-lo sobre isso.
- Não se pode forçar o usuário a selecionar uma rede específica. Após a ligação, terá sempre a rede previamente selecionada, então você precisa observar o chainId no objeto da sessão.
-
Por padrão, a metamask funciona apenas com a Ethereum, mas é possível adicionar outras redes. Atualmente não há forma de utilizar o botão mágico para adicionar uma rede na aplicação nativa, então é necessário publicar quaisquer instruções para isso.
Conclusão
Foi assim que o ciclo de criação de uma aplicação web3 móvel nativa se desenrolou. Sim, agora todas estas tecnologias não funcionam na perfeição, mas todos compreendem que nos últimos anos a nossa vida tem-se esforçado por mover o máximo de funcionalidade possível para os smartphones. Portanto, a revolução da web3 móvel irá acontecer mais cedo ou mais tarde, e nós só podemos tentar aproximá-la :)
Além disso, talvez escreva um artigo sobre o desenvolvimento de uma aplicação web3 completa e haverá informação sobre como trabalhar com bibliotecas web3, chamar contratos inteligentes, etc., se for interessante (no entanto, esta informação é muito mais fácil de encontrar do que informação sobre a Wallet Connect).
Links
Biblioteca modificada da Wallet Connect
Empresa do aplicativo personalizado
Pode também ver o artigo dos meus colegas sobre a integração de contratos inteligentes NFT com OpenSea.
Oldest comments (0)