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.
// 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.
// 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.
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.
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.
// 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.
// 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,
};