From 3d5e5992f8ad40d94f6dcbee384103509c11f37a Mon Sep 17 00:00:00 2001 From: Viterbo Date: Fri, 26 Jan 2024 21:35:13 -0300 Subject: [PATCH] Telos Cloud Login docs --- docs/cloud-login/evm.mdx | 83 +++++++++++++++++ docs/cloud-login/intro.mdx | 23 +++++ docs/cloud-login/zero.mdx | 186 +++++++++++++++++++++++++++++++++++++ docusaurus.config.js | 9 +- sidebars.js | 18 ++++ 5 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 docs/cloud-login/evm.mdx create mode 100644 docs/cloud-login/intro.mdx create mode 100644 docs/cloud-login/zero.mdx diff --git a/docs/cloud-login/evm.mdx b/docs/cloud-login/evm.mdx new file mode 100644 index 0000000..f7fd4b2 --- /dev/null +++ b/docs/cloud-login/evm.mdx @@ -0,0 +1,83 @@ +--- +title: Telos EVM +sidebar_position: 2 +hide_table_of_contents: true +--- + +# Telos Cloud for EVM + +Incorporating **Telos Cloud Login** into a Telos EVM-based application involves creating an instance of Metakeep and then requesting the ethereum provider to create a `ethers.providers.Web3Provider` for interacting with the blockchain. We'll divide the code into two parts. + +## Metakeep Instance + +To create an instance of Metakeep with the proper configuration, execute the following TypeScript code: + +```typescript +import { MetaKeep } from 'metakeep'; +import { ethers } from 'ethers'; + +const email = 'user.address@email.com'; // you take this from google +const appId = 'ad05fb5e-802a-4ae1-18b6-57b9a2626545'; +const chainId = 40; +const url = 'https://mainnet.telos.net'; +const rpcNodeUrls = { + [chainId]: url, +} as unknown as Map; +metakeep = new MetaKeep({ + // App id to configure UI + appId, + // Default chain to use + chainId, + // RPC node urls map + rpcNodeUrls, + // Signed in user's email address + user: { + email, + }, +}); +``` + +## Obtaining the web3Provider + +For Telos EVM, we have a solution provided by Metakeep to construct an instance of `ethers.providers.Web3Provider` from an ethereum provider obtained from the newly created instance of Metakeep. + +```typescript +// we create the web3Provider +const provider = await metakeep.ethereum; +await provider.enable(); +web3Provider = new ethers.providers.Web3Provider(provider); +// finally we get the user account address +this.accountAddress = await provider.getSigner().getAddress(); +``` + +## Sending a Token + +Once the instance of web3Provider is ready, the code to send a TLOS token is straightforward: + + +```typescript +const to = '0x...'; +const value = BigNumber.from(1); +const signer = await provider.getSigner(); +signer.sendTransaction({ to, value }) +``` + +## Interacting with a Contract + +If you need to interact with a contract and you have the ABI and its address, the code is as follows: + + +```typescript +// prepare parameters +const to = '0x...'; +const value = BigNumber.from(1); +const parameters = [ to, value ]; +// prepare contract and method info +const method = 'myMethod'; +const abi = [{name: method, inputs: [...], ... }]; +const contract = '0x...'; +// create a contract instance and interact with it +const signer = await provider.getSigner(); +const contractInstance = new ethers.Contract(contract, abi, signer); +const transaction = await contractInstance[method](...parameters); +``` diff --git a/docs/cloud-login/intro.mdx b/docs/cloud-login/intro.mdx new file mode 100644 index 0000000..c7d407c --- /dev/null +++ b/docs/cloud-login/intro.mdx @@ -0,0 +1,23 @@ +--- +title: "Introduction" +sidebar_position: 1 +hide_table_of_contents: true +--- + +# Telos Cloud Login + +## Overview + +**Telos Cloud Login** offers a seamless access for non-crypto users to enter the web3 space. By utilizing Google for authentication, users can interact with web3 applications by signing transactions on a blockchain without the need to handle private keys or create accounts themselves. + +## How Does It Work? + +At its core, the functionality relies on a third-party service from the Metakeep group, which is responsible for securely managing user keys while also signing transactions on their behalf. Each web3 application will authenticate the user using Google to obtain their email along with their JWT token. These credentials are then used to delegate the transaction signing to Metakeep. + +## EVM vs Zero + +Integrating **Telos Cloud Login** for EVM is significantly more straightforward for two primary reasons: + 1. Account creation is not necessary. The public key provided by Metakeep already serves as the immutable account identifier. + 2. Metakeep offers a web3Provider similar to those injected by MetaMask and other compatible wallets, making the integration process almost identical. + +On the other hand, interaction with Telos Zero presents more complexity due to the Antelope architecture's unique aspects. For starters, it is necessary to associate a public key provided by Metakeep with an account since in Antelope, a public key can be linked to multiple accounts or none at all. Additionally, there isn't a ready-to-use solution from Metakeep for Telos Zero as there is for EVM, thus requiring developers to handle transaction construction, signature requests, and broadcasting. diff --git a/docs/cloud-login/zero.mdx b/docs/cloud-login/zero.mdx new file mode 100644 index 0000000..ad103ba --- /dev/null +++ b/docs/cloud-login/zero.mdx @@ -0,0 +1,186 @@ +--- +title: Telos Zero +sidebar_position: 3 +hide_table_of_contents: true +--- + +# Telos Cloud for Zero + +Unfortunately, for Telos Zero, we do not have a straightforward solution from Metakeep as we do for EVM. Therefore, we need to implement the authentication and transaction processes ourselves. + +## Metakeep Instance + +The first step is to create a Metakeep instance and then request credentials for the user, from which we will obtain the public key for EOS-type (Antelope) blockchains. + +```typescript +// you should get this user's data from google +const email = 'user.address@email.com'; +const jwt = 'eyJhbGciOi...eXAiOiJKV1QifQ.eyJpc...kzMTkifQ.p6AJD-s_ZxJbcx-6fY....bJ_ETidA'; + +// metakeep instance +const appId = 'fbad055e-802a-18b6-4ae1-626557b9a245'; +metakeep = new MetaKeep({ + // App id to configure UI + appId, + // Signed in user's email address + user: { + email, + }, +}); + +const credentials = await metakeep.getWallet(); +const publicKey = credentials.wallet.eosAddress; +``` + +## Account Name Retrieval + +Authenticating requires more than just the public key from Metakeep; at least one account on the blockchain must be associated with that public key. You need to find them out. + +```typescript +// we need the account name for the user +let accountName = ''; +const rpc_endpoint = 'https://api.telos.net'; + +// first, we fetch all the accounts for the email +const response = await axios.post(`${rpc_endpoint}/v1/history/get_key_accounts`, { + public_key: publicKey, +}); +const accountExists = response?.data?.account_names.length>0; + +if (accountExists) { + // for simplicity we take the first one + accountName = response.data.account_names[0]; +} else { + // we get somehow the desired name from the user + const suggestedName = await askTheUserForAnAccountName(); + // we need to create one account (using the jwt) + accountName = await createAccount(publicKey, suggestedName, jwt); +} +``` + +## New Account Creation + +We provide an endpoint in the Telos API for creating an account per user with a simple POST request. + +```typescript +async function createAccount(publicKey, suggestedName, jwt) { + return axios.post(this.accountCreateAPI, { + ownerKey: publicKey, + activeKey: publicKey, + jwt, + suggestedName: suggestedName, + }).then(response => response.data.accountName); +} +``` + +## Sending a Token +Sending a token involves three steps: constructing the transaction, signing it with Metakeep, and broadcasting it to the blockchain. To do so, we will relay heavily on the great `'@greymass/eosio'` library. + +### Create the Transaction +We create a specific object structure containing the raw transaction to be signed, formatted for the Metakeep service. + + +```typescript +import { APIClient, NameType, PackedTransaction, Serializer, Transaction } from '@greymass/eosio'; +const eosioCore = new APIClient({ url: rpc.endpoint }); + +// expire time in seconds +const expireSeconds = 120; + +// Retrieve transaction headers +const info = await eosioCore.v1.chain.get_info(); +const header = info.getTransactionHeader(expireSeconds); + +// collect all contract abis +const abi_promises = originalTransaction.actions.map((a: { account: NameType; }) => + eosioCore.v1.chain.get_abi(a.account), +); +const responses = await Promise.all(abi_promises); +const abis = responses.map(x => x.abi); +const abis_and_names = originalTransaction.actions.map((x: { account: any; }, i: number) => ({ + contract: x.account, + abi: abis[i], +})); + +// create complete well formed transaction +const transaction = Transaction.from( + { + ...header, + actions: originalTransaction.actions, + }, + abis_and_names, +); + +const expiration = transaction.expiration.toString(); +const ref_block_num = transaction.ref_block_num.toNumber(); +const ref_block_prefix = transaction.ref_block_prefix.toNumber(); + +// convert actions to JSON +const actions = transaction.actions.map(a => ({ + account: a.account.toJSON(), + name: a.name.toJSON(), + authorization: a.authorization.map((x: { actor: any; permission: any; }) => ({ + actor: x.actor.toJSON(), + permission: x.permission.toJSON(), + })), + data: a.data.toJSON(), +})); + +// compose the complete transaction +const chainId = '4667b205c6838ef70ff7988f6e8257e8be0e1284a2f59699054a018f743b1d11'; // Telos mainnet +const complete_transaction = { + rawTransaction: { + expiration: expiration, + ref_block_num: ref_block_num, + ref_block_prefix: ref_block_prefix, + max_net_usage_words: 0, + max_cpu_usage_ms: 0, + delay_sec: 0, + context_free_actions: [], + actions: actions, + transaction_extensions: [], + }, + extraSigningData: { + chainId, + }, +}; +``` + +### Sign the Transaction + +Once we have the transaction object, we invoke Metakeep's service to sign it on behalf of the user, which will prompt the user to confirm or reject the signing through an interactive popup. + + +```typescript +// sign the transaction with metakeep +const reason = 'sign this transaction'; +const response = await metakeep.signTransaction(complete_transaction, reason); +const signature = response.signature; +``` + +### Broadcasting the Transaction + +Even after signing, the transaction has not yet been sent and processed by the blockchain, so we must implement this ourselves. + + +```typescript +// Pack the transaction for transport +const packedTransaction = PackedTransaction.from({ + signatures: [signature], + packed_context_free_data: '', + packed_trx: Serializer.encode({ object: transaction }), +}); + +// Broadcast the signed transaction to the blockchain +const pushResponse = await eosioCore.v1.chain.push_transaction( + packedTransaction, +); + +// we compose the final response +const finalResponse/*: SignTransactionResponse*/ = { + wasBroadcast: true, + transactionId: pushResponse.transaction_id, + status: pushResponse.processed.receipt.status, + transaction: packedTransaction, +}; +``` diff --git a/docusaurus.config.js b/docusaurus.config.js index 7de340d..7d0997a 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -82,13 +82,13 @@ const config = { items: [ { type: 'docSidebar', - label: 'Overview', sidebarId: 'overview', + label: 'Overview', }, { type: 'docSidebar', - label: 'Quick Start', sidebarId: 'quickStart', + label: 'Quick Start', }, { type: 'docSidebar', @@ -105,6 +105,11 @@ const config = { sidebarId: 'zero', label: 'Telos Zero', }, + { + type: 'docSidebar', + sidebarId: 'cloudLogin', + label: 'Telos Cloud Login', + }, { type: 'docSidebar', sidebarId: 'nodes', diff --git a/sidebars.js b/sidebars.js index 0831a24..c1d3811 100644 --- a/sidebars.js +++ b/sidebars.js @@ -315,6 +315,24 @@ const sidebars = { 'zero/obe_multisig' ], + // Telos Cloud Login + cloudLogin: [ + { + type: 'category', + label: 'Telos Cloud Login', + collapsed: true, + items: [ + { + type: 'autogenerated', + dirName: 'cloud-login', + }, + ], + } + // 'cloud-login/intro', + // 'cloud-login/evm', + // 'cloud-login/zero', + ], + // Nodes nodes: [ 'nodes/Nodes and Clients',