diff --git a/README.md b/README.md index 0446d3bac9..925bf2ce0a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Taquito is used by **over 80% of DApps** in the Tezos ecosystem. It is easy to u ## Why should I use Taquito? -Taquito provides convenient abstractions for a multitude of common operations, including wallet interactions (with [WalletConnect2](https://docs.walletconnect.com/2.0) in the works), batching operations, calling into contracts, querying the blockchain, and more. Taquito will isolate your code from subtle - and some not-so-subtle - changes made to the underlying Tezos protocol. +Taquito provides convenient abstractions for a multitude of common operations, including wallet interactions (with [WalletConnect/Reown](https://reown.com/) in the works), batching operations, calling into contracts, querying the blockchain, and more. Taquito will isolate your code from subtle - and some not-so-subtle - changes made to the underlying Tezos protocol. ...Not to mention our thriving, helpful, and welcoming community! @@ -80,6 +80,8 @@ Taquito is organized as a [monorepo](https://en.wikipedia.org/wiki/Monorepo), an | [@taquito/contracts-library](packages/taquito-contracts-library) | Provides functionality specify static data related to contracts | | [@taquito/ledger-signer](packages/taquito-ledger-signer) | Provides functionality for ledger signer provider | | [@taquito/timelock](packages/taquito-timelock) | Provides functionality to create and open timelocks | +| [@taquito/wallet-connect](packages/taquito-wallet-connect) | WalletConnect class can be injected into the `TezosToolkit` to work with the wallet API. | + ## API Documentation diff --git a/apps/taquito-test-dapp/package.json b/apps/taquito-test-dapp/package.json index a06b83bec3..6d706e34b3 100644 --- a/apps/taquito-test-dapp/package.json +++ b/apps/taquito-test-dapp/package.json @@ -15,7 +15,7 @@ "events": "^3.3.0", "lerna": "^8.1.7", "prettier-plugin-svelte": "^3.2.6", - "sass": "^1.78.0", + "sass": "^1.80.1", "svelte": "^4.2.19", "svelte-check": "^3.8.5", "svelte-preprocess": "^6.0.2", @@ -30,7 +30,9 @@ "@taquito/core": "^20.1.0", "@taquito/taquito": "^20.1.0", "@taquito/utils": "^20.1.0", + "@taquito/wallet-connect": "^20.1.0", "buffer": "^6.0.3", + "svelte-modals": "^2.0.0-beta.2", "svelte-select": "^5.8.3", "vite-compatible-readable-stream": "^3.6.1" }, diff --git a/apps/taquito-test-dapp/src/App.svelte b/apps/taquito-test-dapp/src/App.svelte index c3447e11a2..f1e75b5ab6 100644 --- a/apps/taquito-test-dapp/src/App.svelte +++ b/apps/taquito-test-dapp/src/App.svelte @@ -4,7 +4,7 @@ import { NetworkType } from "@airgap/beacon-types"; import Select from "svelte-select"; import { getRpcUrl } from "./config"; - import store from "./store"; + import store, { SDK } from "./store"; import Layout from "./Layout.svelte"; import TestContainer from "./lib/TestContainer.svelte"; @@ -13,7 +13,6 @@ let browser = ""; let availableNetworks = [ { value: "ghostnet", label: "Ghostnet", group: "current testnets" }, - { value: "oxfordnet", label: "Oxfordnet", group: "current testnets" }, { value: "parisnet", label: "Parisnet", group: "current testnets" }, { value: "mainnet", label: "Mainnet", group: "mainnet" }, { value: "dailynet", label: "Dailynet", group: "other testnets" }, @@ -40,9 +39,6 @@ case "ghostnet": store.updateNetworkType(NetworkType.GHOSTNET); break; - case "oxfordnet": - store.updateNetworkType(NetworkType.OXFORDNET); - break; case "parisnet": store.updateNetworkType(NetworkType.PARISNET); break; @@ -129,6 +125,18 @@ margin: 10px 0px; } + .sdk { + border: 1px solid; + border-radius: 10px; + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 20px; + grid-row-gap: 10px; + align-items: center; + width: 100%; + } + button { width: 100%; justify-content: center; @@ -175,15 +183,32 @@
(use Chrome for a better experience)
{/if}
- +
+ Connect using beacon sdk + +
+
+ Connect using Wallet Connect + +
+ {/each} +

or

+ +
+ + +{/if} \ No newline at end of file diff --git a/apps/taquito-test-dapp/src/lib/TestContainer.svelte b/apps/taquito-test-dapp/src/lib/TestContainer.svelte index 89c466b26f..2896d5bf33 100644 --- a/apps/taquito-test-dapp/src/lib/TestContainer.svelte +++ b/apps/taquito-test-dapp/src/lib/TestContainer.svelte @@ -5,14 +5,26 @@ import { shortenHash } from "../utils"; import { NetworkType } from "@airgap/beacon-types"; import { getTzKtUrl } from "../config"; + import { BeaconWallet } from "@taquito/beacon-wallet"; let test: TestSettings | undefined; let executionTime = 0; let loading = false; let success: boolean | undefined; let opHash = ""; - let input = { text: "", fee: 400, storageLimit: 400, gasLimit: 1320, amount: 0, address: "", delegate: "", stake: 0, unstake: 0 }; + let input = { + text: "", + fee: 400, + storageLimit: 400, + gasLimit: 1320, + amount: 0, + address: "", + delegate: "", + stake: 0, + unstake: 0, + }; let testResult: { id: string; title: string; body: any }; + let error: Error | undefined; const run = async () => { success = undefined; @@ -68,9 +80,18 @@ title: "Confirmations through observable", body: result.confirmationObsOutput, }; + } else if (test.id === "show-public-key") { + testResult = { + id: test.id, + title: "Public Key", + body: { + output: result.output + } + } } } else { - throw "Error"; + error = result.error; + throw result.error; } } catch (error) { console.log(error); @@ -82,7 +103,9 @@ }; const switchAccount = async () => { - await $store.wallet.clearActiveAccount(); + if ($store.wallet instanceof BeaconWallet) { + await $store.wallet.clearActiveAccount(); + } store.updateUserAddress(undefined); store.updateUserBalance(undefined); store.updateWallet(undefined); @@ -306,21 +329,21 @@ bind:value={input.text} /> - {:else if test.inputRequired && test.inputType === "delegate"} + {:else if test.inputRequired && test.inputType === "delegate"}
- {:else if test.inputRequired && test.inputType === "stake"} + {:else if test.inputRequired && test.inputType === "stake"}
- {:else if test.inputRequired && test.inputType === "unstake"} + {:else if test.inputRequired && test.inputType === "unstake"}
{/if}
diff --git a/apps/taquito-test-dapp/src/lib/Wallet.svelte b/apps/taquito-test-dapp/src/lib/Wallet.svelte index e14fb992f2..839578a334 100644 --- a/apps/taquito-test-dapp/src/lib/Wallet.svelte +++ b/apps/taquito-test-dapp/src/lib/Wallet.svelte @@ -3,21 +3,38 @@ import { fly } from "svelte/transition"; import { TezosToolkit } from "@taquito/taquito"; import { BeaconWallet } from "@taquito/beacon-wallet"; - import { BeaconEvent, type DAppClientOptions } from "@airgap/beacon-sdk"; - import store from "../store"; + import store, { SDK } from "../store"; import { formatTokenAmount, shortenHash } from "../utils"; - import { - defaultMatrixNode, - getRpcUrl, - defaultNetworkType, - type SupportedNetworks, - } from "../config"; + import { defaultMatrixNode, getRpcUrl, type SupportedNetworks } from "../config"; import type { TezosAccountAddress } from "../types"; + import { WalletConnect, PermissionScopeMethods, NetworkType as NetworkTypeWc2 } from "@taquito/wallet-connect"; + import { Modals, closeModal, openModal } from "svelte-modals"; + import ModalActivePairing from "./ModalActivePairing.svelte"; + import { type NetworkType as NetworkTypeBeacon, BeaconEvent } from "@airgap/beacon-sdk"; let showDialog = false; let connectedWallet = ""; - const createNewWallet = (config: { networkType: SupportedNetworks }) => { + const selectExistingPairing = (wallet: WalletConnect, existingPairing: any[]) => { + openModal( + ModalActivePairing, + { + title: "Select available pairing", + options: existingPairing, + }, + { + on: { + select: async (event) => { + closeModal(); + const topic = event.detail === "new_pairing" ? undefined : event.detail.topic; + await requestPermissionWc2(wallet, topic); + }, + }, + } + ); + }; + + const createNewBeaconWallet = (config: { networkType: SupportedNetworks }) => { const wallet = new BeaconWallet({ name: "Taquito Test Dapp", matrixNodes: [defaultMatrixNode] as any, @@ -26,7 +43,7 @@ rpcUrl: getRpcUrl(config.networkType), }, walletConnectOptions: { - projectId: "ba97fd7d1e89eae02f7c330e14ce1f36", + projectId: "ba97fd7d1e89eae02f7c330e14ce1f36", // Project ID can be obtained from Reown Cloud (https://cloud.reown.com). }, enableMetrics: $store.enableMetrics, }); @@ -34,74 +51,177 @@ return wallet; }; - const connectWallet = async () => { - const wallet = await setWallet({ - networkType: $store.networkType, + const createNewWalletConnect = async () => { + const wallet = await WalletConnect.init({ + logger: "debug", + projectId: "ba97fd7d1e89eae02f7c330e14ce1f36", // Project ID can be obtained from Reown Cloud (https://cloud.reown.com). + metadata: { + name: "Taquito Test Dapp", + description: "Test Taquito with WalletConnect", + icons: [], + url: "", + }, + }); + wallet.signClient.on("session_ping", ({ id, topic }) => { + console.log("session_ping in test dapp", id, topic); + store.addEvent("session_ping"); + }); + wallet.signClient.on("session_delete", ({ topic }) => { + console.log("EVENT: session_delete", topic); + store.addEvent("session_delete"); + if (!wallet.isActiveSession()) { + resetApp(); + } }); + wallet.signClient.on("session_update", async ({ topic }) => { + console.log("EVENT: session_update", topic); + store.addEvent("session_update"); + const allAccounts = wallet.getAccounts(); + await updateStore(wallet, allAccounts); + }); + return wallet; + }; - await subscribeToAllEvents(wallet); + const requestPermissionWc2 = async (wallet: WalletConnect, pairingTopic?: string) => { + await wallet.requestPermissions({ + permissionScope: { + networks: [$store.networkType as NetworkTypeWc2], + events: [], + methods: [PermissionScopeMethods.TEZOS_SEND, PermissionScopeMethods.TEZOS_SIGN, PermissionScopeMethods.TEZOS_GET_ACCOUNTS], + }, + registryUrl: `https://explorer-api.walletconnect.com/v3/wallets?projectId=ba97fd7d1e89eae02f7c330e14ce1f36`, // Project ID can be obtained from Reown(Walletconnect) Cloud (https://cloud.reown.com). + pairingTopic + }); + const allAccounts = wallet.getAccounts(); + await updateStore(wallet, allAccounts); + }; - try { - await wallet.requestPermissions(); + const connectWalletWithExistingSession = async (sessionId: string) => { + const newWallet = await createNewWalletConnect(); + newWallet.configureWithExistingSessionKey(sessionId); + const allAccounts = newWallet.getAccounts(); + await updateStore(newWallet, allAccounts); + }; - const userAddress = (await wallet.getPKH()) as TezosAccountAddress; - store.updateUserAddress(userAddress); - const url = getRpcUrl($store.networkType); - const Tezos = new TezosToolkit(url); - Tezos.setWalletProvider(wallet); - store.updateTezos(Tezos); + const connectWallet = async () => { + if (!$store.wallet) { + if ($store.sdk === SDK.BEACON) { + const newWallet = createNewBeaconWallet({networkType: $store.networkType}); + await newWallet.requestPermissions({ + network: { + type: $store.networkType as NetworkTypeBeacon, + rpcUrl: getRpcUrl($store.networkType), + }, + }); - const balance = await Tezos.tz.getBalance(userAddress); - if (balance) { - store.updateUserBalance(balance.toNumber()); + const peers = await newWallet.client.getPeers(); + connectedWallet = peers[0].name; + await updateStore(newWallet); + } else if ($store.sdk === SDK.WC2) { + const newWallet = await createNewWalletConnect(); + const existingPairing = newWallet.getAvailablePairing(); + if (existingPairing.length > 0) { + selectExistingPairing(newWallet, existingPairing); + } else { + await requestPermissionWc2(newWallet); + } } + } else { + return $store.wallet; + } + }; + const updateUserBalance = async (userAddress: string) => { + const balance = await $store.Tezos!.tz.getBalance(userAddress); + if (balance) { + store.updateUserBalance(balance.toNumber()); + } + }; + + const updateStore = async (wallet: BeaconWallet | WalletConnect, allAccounts?: string[]) => { + try { store.updateWallet(wallet); + let userAddress: TezosAccountAddress; + if (allAccounts) { + if (allAccounts.length > 1) { + userAddress = allAccounts.shift() as TezosAccountAddress; + store.updateAvailableAccounts(allAccounts); + } else { + store.updateAvailableAccounts([]); + userAddress = allAccounts[0] as TezosAccountAddress; + } + } else { + userAddress = (await wallet.getPKH()) as TezosAccountAddress; + } + store.updateUserAddress(userAddress); + if (wallet instanceof WalletConnect) { + wallet.setActiveAccount(userAddress); + wallet.setActiveNetwork($store.networkType as any); + } + + const Tezos = new TezosToolkit(getRpcUrl($store.networkType)); + Tezos.setWalletProvider(wallet); + store.updateTezos(Tezos); - const peers = await wallet.client.getPeers(); - connectedWallet = peers[0].name; + await updateUserBalance(userAddress); } catch (err) { console.error(err); } }; - const disconnectWallet = async () => { - await $store.wallet?.clearActiveAccount(); + const resetApp = async () => { store.updateUserAddress(undefined); store.updateUserBalance(undefined); store.updateWallet(undefined); store.updateSelectedTest(undefined); + store.updateTests([]); + store.updateAvailableAccounts([]); }; - export const setWallet = async (config: { networkType: SupportedNetworks }) => { - store.updateNetworkType(config.networkType); - - const wallet = createNewWallet(config); - store.updateWallet(wallet); - const url = getRpcUrl(config.networkType); - const Tezos = new TezosToolkit(url); - Tezos.setWalletProvider(wallet); - store.updateTezos(Tezos); - - // const activeAccount = await wallet.client.getActiveAccount(); - // if (activeAccount) { - // const userAddress = (await wallet.getPKH()) as TezosAccountAddress; - // store.updateUserAddress(userAddress); + const disconnectWallet = async () => { + if ($store.wallet instanceof BeaconWallet) { + await $store.wallet.clearActiveAccount(); + } else if ($store.wallet instanceof WalletConnect) { + await $store.wallet.disconnect(); + } + resetApp(); + }; - // const balance = await Tezos.tz.getBalance(userAddress); - // if (balance) { - // store.updateUserBalance(balance.toNumber()); - // } - // } - return wallet; + const switchActiveAccount = (newActiveAccount: string) => { + const currentPkh = $store.userAddress; + const availablePkh = $store.availableAccounts; + const index = availablePkh!.indexOf(newActiveAccount, 0); + if (index > -1) { + availablePkh!.splice(index, 1); + } + availablePkh!.push(currentPkh!); + store.updateAvailableAccounts(availablePkh!); + store.updateUserAddress(newActiveAccount); + if ($store.wallet instanceof WalletConnect) { + $store.wallet.setActiveAccount(newActiveAccount); + } + updateUserBalance(newActiveAccount); }; onMount(async () => { - store.updateNetworkType(defaultNetworkType); + console.log("onmount wallet", $store); + if ( + window && + window.localStorage && + window.localStorage["wc@2:client:0.3//session"] && + window.localStorage["wc@2:client:0.3//session"] !== "[]" + ) { + const sessions = JSON.parse(window.localStorage["wc@2:client:0.3//session"]); + const lastSession = sessions[sessions.length - 1].topic; + store.updateSdk(SDK.WC2); + await connectWalletWithExistingSession(lastSession); + } else { + await connectWallet(); + } }); afterUpdate(async () => { - if ($store.wallet) { + if ($store.wallet instanceof BeaconWallet) { const activeAccount = await $store.wallet.client.getActiveAccount(); if (activeAccount) { const peers = await $store.wallet.client.getPeers(); @@ -109,92 +229,10 @@ connectedWallet = peers[0].name; } } + } else if ($store.wallet instanceof WalletConnect) { + connectedWallet = $store.wallet.getPeerMetadata().name; } }); - - const saveLog = (data: unknown, eventType: BeaconEvent) => { - const log = JSON.stringify({ eventType, data }); - store.addEvent(log); - }; - - async function subscribeToAllEvents(wallet: BeaconWallet) { - await wallet.client.subscribeToEvent(BeaconEvent.PERMISSION_REQUEST_SENT, (data) => - saveLog(data, BeaconEvent.PERMISSION_REQUEST_SENT), - ); - await wallet.client.subscribeToEvent(BeaconEvent.PERMISSION_REQUEST_SUCCESS, (data) => - saveLog(data, BeaconEvent.PERMISSION_REQUEST_SUCCESS), - ); - await wallet.client.subscribeToEvent(BeaconEvent.PERMISSION_REQUEST_ERROR, (data) => - saveLog(data, BeaconEvent.PERMISSION_REQUEST_ERROR), - ); - await wallet.client.subscribeToEvent(BeaconEvent.OPERATION_REQUEST_SENT, (data) => - saveLog(data, BeaconEvent.OPERATION_REQUEST_SENT), - ); - await wallet.client.subscribeToEvent(BeaconEvent.OPERATION_REQUEST_SUCCESS, (data) => - saveLog(data, BeaconEvent.OPERATION_REQUEST_SUCCESS), - ); - await wallet.client.subscribeToEvent(BeaconEvent.OPERATION_REQUEST_ERROR, (data) => - saveLog(data, BeaconEvent.OPERATION_REQUEST_ERROR), - ); - await wallet.client.subscribeToEvent(BeaconEvent.SIGN_REQUEST_SENT, (data) => - saveLog(data, BeaconEvent.SIGN_REQUEST_SENT), - ); - await wallet.client.subscribeToEvent(BeaconEvent.SIGN_REQUEST_SUCCESS, (data) => - saveLog(data, BeaconEvent.SIGN_REQUEST_SUCCESS), - ); - await wallet.client.subscribeToEvent(BeaconEvent.SIGN_REQUEST_ERROR, (data) => - saveLog(data, BeaconEvent.SIGN_REQUEST_ERROR), - ); - await wallet.client.subscribeToEvent(BeaconEvent.BROADCAST_REQUEST_SENT, (data) => - saveLog(data, BeaconEvent.BROADCAST_REQUEST_SENT), - ); - await wallet.client.subscribeToEvent(BeaconEvent.BROADCAST_REQUEST_SUCCESS, (data) => - saveLog(data, BeaconEvent.BROADCAST_REQUEST_SUCCESS), - ); - await wallet.client.subscribeToEvent(BeaconEvent.BROADCAST_REQUEST_ERROR, (data) => - saveLog(data, BeaconEvent.BROADCAST_REQUEST_ERROR), - ); - await wallet.client.subscribeToEvent(BeaconEvent.ACKNOWLEDGE_RECEIVED, (data) => - saveLog(data, BeaconEvent.ACKNOWLEDGE_RECEIVED), - ); - await wallet.client.subscribeToEvent(BeaconEvent.LOCAL_RATE_LIMIT_REACHED, (data) => - saveLog(data, BeaconEvent.LOCAL_RATE_LIMIT_REACHED), - ); - await wallet.client.subscribeToEvent(BeaconEvent.NO_PERMISSIONS, (data) => - saveLog(data, BeaconEvent.NO_PERMISSIONS), - ); - - await wallet.client.subscribeToEvent(BeaconEvent.ACTIVE_ACCOUNT_SET, (data) => { - saveLog(data, BeaconEvent.ACTIVE_ACCOUNT_SET); - store.updateUserAddress(data.address); - store.updateNetworkType(data.network.type as SupportedNetworks); - }); - - await wallet.client.subscribeToEvent(BeaconEvent.ACTIVE_TRANSPORT_SET, (data) => - saveLog(data, BeaconEvent.ACTIVE_TRANSPORT_SET), - ); - await wallet.client.subscribeToEvent(BeaconEvent.SHOW_PREPARE, (data) => - saveLog(data, BeaconEvent.SHOW_PREPARE), - ); - await wallet.client.subscribeToEvent(BeaconEvent.HIDE_UI, (data) => - saveLog(data, BeaconEvent.HIDE_UI), - ); - await wallet.client.subscribeToEvent(BeaconEvent.PAIR_INIT, (data) => - saveLog(data, BeaconEvent.PAIR_INIT), - ); - await wallet.client.subscribeToEvent(BeaconEvent.PAIR_SUCCESS, (data) => - saveLog(data, BeaconEvent.PAIR_SUCCESS), - ); - await wallet.client.subscribeToEvent(BeaconEvent.CHANNEL_CLOSED, (data) => - saveLog(data, BeaconEvent.CHANNEL_CLOSED), - ); - await wallet.client.subscribeToEvent(BeaconEvent.INTERNAL_ERROR, (data) => - saveLog(data, BeaconEvent.INTERNAL_ERROR), - ); - await wallet.client.subscribeToEvent(BeaconEvent.UNKNOWN, (data) => - saveLog(data, BeaconEvent.UNKNOWN), - ); - }