Skip to content

Telos Cloud Login docs #169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions docs/cloud-login/evm.mdx
Original file line number Diff line number Diff line change
@@ -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 = '[email protected]'; // 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<number, string>;
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);
```
23 changes: 23 additions & 0 deletions docs/cloud-login/intro.mdx
Original file line number Diff line number Diff line change
@@ -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.
186 changes: 186 additions & 0 deletions docs/cloud-login/zero.mdx
Original file line number Diff line number Diff line change
@@ -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 = '[email protected]';
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,
};
```
9 changes: 7 additions & 2 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -105,6 +105,11 @@ const config = {
sidebarId: 'zero',
label: 'Telos Zero',
},
{
type: 'docSidebar',
sidebarId: 'cloudLogin',
label: 'Telos Cloud Login',
},
{
type: 'docSidebar',
sidebarId: 'nodes',
Expand Down
18 changes: 18 additions & 0 deletions sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down