Skip to content
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

LedgerDappProvider #43

Open
wants to merge 8 commits into
base: development
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- [Added transaction manager](https://github.com/multiversx/mx-sdk-dapp-core/pull/41)
- [Added custom web socket url support](https://github.com/multiversx/mx-sdk-dapp-core/pull/35)
- [Metamask integration](https://github.com/multiversx/mx-sdk-dapp-core/pull/27)
- [Extension integration](https://github.com/multiversx/mx-sdk-dapp-core/pull/26)
Expand Down
1 change: 1 addition & 0 deletions src/constants/transactions.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export const CROSS_SHARD_ROUNDS = 5;
export const TRANSACTIONS_STATUS_POLLING_INTERVAL_MS = 90 * 1000; // 90sec
export const TRANSACTIONS_STATUS_DROP_INTERVAL_MS = 10 * 60 * 1000; // 10min
export const CANCEL_TRANSACTION_TOAST_DEFAULT_DURATION = 20000;
export const BATCH_TRANSACTIONS_ID_SEPARATOR = '-';
142 changes: 142 additions & 0 deletions src/core/managers/TransactionManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { Transaction } from '@multiversx/sdk-core/out';
import axios, { AxiosError } from 'axios';
import { BATCH_TRANSACTIONS_ID_SEPARATOR } from 'constants/transactions.constants';
import { getAccount } from 'core/methods/account/getAccount';
import { networkSelector } from 'store/selectors';
import { getState } from 'store/store';
import { GuardianActionsEnum } from 'types';
import { BatchTransactionsResponseType } from 'types/serverTransactions.types';
import { SignedTransactionType } from 'types/transactions.types';

export class TransactionManager {
public static send = async (
signedTransactions: Transaction[] | Transaction[][]
): Promise<string[]> => {
if (signedTransactions.length === 0) {
throw new Error('No transactions to send');
}

try {
const isBatchTransaction =
TransactionManager.isBatchTransaction(signedTransactions);

if (!isBatchTransaction) {
const hashes = await this.sendSignedTransactions(signedTransactions);
return hashes;
}

const sentTransactions =
await this.sendSignedBatchTransactions(signedTransactions);

if (!sentTransactions.data || sentTransactions.data.error) {
throw new Error(
sentTransactions.data?.error || 'Failed to send transactions'
);
}

const flatSentTransactions = this.sequentialToFlatArray(
sentTransactions.data.transactions
);

return flatSentTransactions.map((transaction) => transaction.hash);
} catch (error) {
const responseData = <{ message: string }>(
(error as AxiosError).response?.data
);
throw responseData?.message ?? (error as any).message;
}
};

private static sendSignedTransactions = async (
signedTransactions: Transaction[]
): Promise<string[]> => {
const { apiAddress, apiTimeout } = networkSelector(getState());

const promises = signedTransactions.map((transaction) =>
axios.post(`${apiAddress}/transactions`, transaction.toPlainObject(), {
timeout: Number(apiTimeout)
})
);

const response = await Promise.all(promises);

return response.map(({ data }) => data.txHash);
};

private static sendSignedBatchTransactions = async (
signedTransactions: Transaction[][]
) => {
const { address } = getAccount();
const { apiAddress, apiTimeout } = networkSelector(getState());

if (!address) {
return {
error:
'Invalid address provided. You need to be logged in to send transactions'
};
}

const batchId = this.buildBatchId(address);
const parsedTransactions = signedTransactions.map((transactions) =>
transactions.map((transaction) =>
this.parseSignedTransaction(transaction)
)
);

const payload = {
transactions: parsedTransactions,
id: batchId
};

const { data } = await axios.post<BatchTransactionsResponseType>(
`${apiAddress}/batch`,
payload,
{
timeout: Number(apiTimeout)
}
);

return { data };
};

private static buildBatchId = (address: string) => {
const sessionId = Date.now().toString();
return `${sessionId}${BATCH_TRANSACTIONS_ID_SEPARATOR}${address}`;
};

private static sequentialToFlatArray = (
transactions: SignedTransactionType[] | SignedTransactionType[][] = []
) =>
this.getIsSequential(transactions)
? transactions.flat()
: (transactions as SignedTransactionType[]);

private static getIsSequential = (
transactions?: SignedTransactionType[] | SignedTransactionType[][]
) => transactions?.every((transaction) => Array.isArray(transaction));

private static isBatchTransaction = (
transactions: Transaction[] | Transaction[][]
): transactions is Transaction[][] => {
return Array.isArray(transactions[0]);
};

private static parseSignedTransaction = (signedTransaction: Transaction) => {
const parsedTransaction = {
...signedTransaction.toPlainObject(),
hash: signedTransaction.getHash().hex()
};

// TODO: Remove when the protocol supports usernames for guardian transactions
if (this.isGuardianTx(parsedTransaction.data)) {
delete parsedTransaction.senderUsername;
delete parsedTransaction.receiverUsername;
}

return parsedTransaction;
};

private static isGuardianTx = (transactionData?: string) =>
transactionData &&
transactionData.startsWith(GuardianActionsEnum.SetGuardian);
}

This file was deleted.

26 changes: 0 additions & 26 deletions src/core/methods/sendTransactions/sendTransactions.ts

This file was deleted.

9 changes: 6 additions & 3 deletions src/core/providers/DappProvider/DappProvider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Message } from '@multiversx/sdk-core/out/message';
import { Transaction } from '@multiversx/sdk-core/out/transaction';
import { emptyProvider } from '../helpers/emptyProvider';
import { IProvider } from '../types/providerFactory.types';
import { login } from './helpers/login/login';
import { logout } from './helpers/logout/logout';
Expand All @@ -14,10 +15,12 @@ import {
} from './helpers/signTransactions/signTransactions';

export class DappProvider {
private provider: IProvider;
protected provider: IProvider;
protected address: string = '';

constructor(provider: IProvider) {
this.provider = provider;
constructor(address?: string) {
this.provider = emptyProvider;
this.address = address || '';
}

init() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Transaction } from '@multiversx/sdk-core/out';
import { IProvider } from 'core/providers/types/providerFactory.types';

interface ISignWithUIProps {
transactions: Transaction[];
provider: IProvider;
}

export const getSignedTransactions = async ({
transactions,
provider
}: ISignWithUIProps): Promise<Transaction[]> => {
if (!provider.mountSignUI) {
const signedTransactions = await provider.signTransactions(transactions);
return signedTransactions ?? [];
}

provider.mountSignUI();

return transactions;
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { getAccount } from 'core/methods/account/getAccount';
import { IProvider } from 'core/providers/types/providerFactory.types';
import { getGuardedTransactions } from './helpers/getGuardedTransactions';
import { getSignedTransactions } from './helpers/getSignedTransactions';

export type SignTransactionsOptionsType = {
skipGuardian?: boolean;
Expand Down Expand Up @@ -37,8 +38,10 @@ export async function signTransactions({
})
: transactions;

const signedTransactions: Transaction[] =
(await provider.signTransactions(transacitonsToSign)) ?? [];
const signedTransactions = await getSignedTransactions({
transactions: transacitonsToSign,
provider
});

const guardedTransactions = isGuarded
? await getGuardedTransactions({ transactions: signedTransactions })
Expand Down
41 changes: 11 additions & 30 deletions src/core/providers/ProviderFactory.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { IframeLoginTypes } from '@multiversx/sdk-web-wallet-iframe-provider/out/constants';
import { SECOND_LOGIN_ATTEMPT_ERROR } from 'constants/errorMessages.constants';
import { getAddress } from 'core/methods/account/getAddress';
import { getIsLoggedIn } from 'core/methods/account/getIsLoggedIn';
import { setProviderType } from 'store/actions/loginInfo/loginInfoActions';
import { setAccountProvider } from './accountProvider';
import { DappProvider } from './DappProvider/DappProvider';
import { createCrossWindowProvider } from './helpers/crossWindow/createCrossWindowProvider';
import { createExtensionProvider } from './helpers/extension/createExtensionProvider';
import { getConfig } from './helpers/getConfig';
import { createIframeProvider } from './helpers/iframe/createIframeProvider';
import { createLedgerProvider } from './helpers/ledger/createLedgerProvider';
import { LedgerDappProvider } from './helpers/ledger/LedgerDappProvider';
import {
ICustomProvider,
IProvider,
Expand All @@ -26,17 +23,17 @@ export class ProviderFactory {

public static async create({
type,
config: userConfig
config: userConfig // TODO: remove config and get address from store
}: IProviderFactory): Promise<DappProvider> {
let createdProvider: IProvider | null = null;
const config = await getConfig(userConfig);
const { account, UI } = config;
const { account } = config;
let dappProvider: DappProvider | null = null;

switch (type) {
case ProviderTypeEnum.extension: {
const provider = await createExtensionProvider();
const provider = await createExtensionProvider(); // TODO: make classes
createdProvider = provider as unknown as IProvider;

createdProvider.getType = () => ProviderTypeEnum.extension;

break;
Expand All @@ -54,24 +51,8 @@ export class ProviderFactory {
}

case ProviderTypeEnum.ledger: {
const ledgerProvider = await createLedgerProvider(UI.ledger.mount);

if (!ledgerProvider) {
throw new Error('Unable to create ledger provider');
}

createdProvider = ledgerProvider;

createdProvider.getType = () => ProviderTypeEnum.ledger;

const loggedIn = getIsLoggedIn();

if (loggedIn) {
console.warn('Already logged in with:', getAddress());
throw new Error(SECOND_LOGIN_ATTEMPT_ERROR);
}

await createdProvider.init?.();
dappProvider = new LedgerDappProvider();
await dappProvider.init?.();

break;
}
Expand Down Expand Up @@ -113,19 +94,19 @@ export class ProviderFactory {
default: {
for (const customProvider of this._customProviders) {
if (customProvider.type === type) {
createdProvider = await customProvider.constructor(config);
createdProvider.getType = () => type;
dappProvider = new customProvider.constructor();
dappProvider.getType = () => type;
}
}
break;
}
}

if (!createdProvider) {
if (dappProvider == null) {
throw new Error('Unable to create provider');
}

const dappProvider = new DappProvider(createdProvider);
// dappProvider = new DappProvider(createdProvider);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still need ?


setAccountProvider(dappProvider);
setProviderType(type as ProviderTypeEnum);
Expand Down
2 changes: 1 addition & 1 deletion src/core/providers/accountProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { emptyProvider } from './helpers/emptyProvider';

export type ProvidersType = IProvider;

let accountProvider: DappProvider = new DappProvider(emptyProvider);
let accountProvider: DappProvider = new DappProvider();

export function setAccountProvider<TProvider extends DappProvider>(
provider: TProvider
Expand Down
Loading
Loading