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

Ledger provider #25

Merged
merged 25 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e098464
add todos
CiprianDraghici Sep 4, 2024
2b77ed7
Merge remote-tracking branch 'refs/remotes/origin/development' into c…
CiprianDraghici Sep 6, 2024
5cb04b6
update packages and add TODO
CiprianDraghici Sep 6, 2024
de787de
Work on ledger provider
arhtudormorar Sep 16, 2024
d210306
Project building
arhtudormorar Sep 17, 2024
1236242
Signing with leder working
arhtudormorar Sep 17, 2024
4b6d84b
update packages
CiprianDraghici Sep 17, 2024
96ab412
Merge remote-tracking branch 'origin/development' into cd/feature/sta…
CiprianDraghici Sep 17, 2024
49bcab1
fix build
CiprianDraghici Sep 17, 2024
c2c1fa5
update signMessage method and add TODOs for next steps
CiprianDraghici Sep 17, 2024
7064b57
Upgrade packages
arhtudormorar Sep 19, 2024
cfcb9f3
Remove unused
arhtudormorar Sep 19, 2024
7502e92
Merge branch 'cd/feature/standardize-providers' into tm/ledger-provider
arhtudormorar Sep 19, 2024
d461db4
Update yarn lock
arhtudormorar Sep 19, 2024
aedb8d3
Try alpha 1
arhtudormorar Sep 19, 2024
aa4857b
Upgrade cwp
arhtudormorar Sep 19, 2024
28343a5
Added jest types
arhtudormorar Sep 19, 2024
3479319
Fix guardian signing
arhtudormorar Sep 19, 2024
fd8a408
Upgrade ledger
arhtudormorar Sep 19, 2024
e03790e
Merge development
arhtudormorar Sep 19, 2024
b738791
Resolve comments
arhtudormorar Sep 20, 2024
0e4d9b2
Separate react store from main store
arhtudormorar Sep 23, 2024
8ce464a
Upgrade empty provider
arhtudormorar Sep 23, 2024
4e1420f
Remove callbackRoute from signMessage and upgrade to Message
arhtudormorar Sep 23, 2024
ad4c2ef
Revert version
arhtudormorar Sep 23, 2024
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
24 changes: 13 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"scripts": {
"compile": "tsc && tsc-alias",
"build-esbuild": "rimraf out && node esbuild.js",
"publish-verdaccio": "npm unpublish --registry http://localhost:4873 @multiversx/[email protected].3 && rimraf out && yarn compile && npm publish --registry http://localhost:4873",
"publish-verdaccio": "npm unpublish --registry http://localhost:4873 @multiversx/[email protected].11 && rimraf out && yarn compile && npm publish --registry http://localhost:4873",
"build": "yarn build-esbuild && yarn compile",
"test": "jest",
"compile-next": "rimraf out && tsc --p tsconfig.next.json && tsc-alias --project tsconfig.next.json"
Expand All @@ -32,7 +32,7 @@
"dependencies": {
"@lifeomic/axios-fetch": "3.0.1",
"@multiversx/sdk-extension-provider": "4.0.0-alpha.0",
"@multiversx/sdk-hw-provider": "6.4.0",
"@multiversx/sdk-hw-provider": "7.0.0",
"@multiversx/sdk-metamask-provider": "0.0.7",
"@multiversx/sdk-native-auth-client": "^1.0.8",
"@multiversx/sdk-opera-provider": "1.0.0-alpha.1",
Expand All @@ -41,26 +41,29 @@
"@multiversx/sdk-web-wallet-provider": "3.2.1",
"isomorphic-fetch": "3.0.0",
"lodash": "4.17.21",
"protobufjs": "7.3.0",
"socket.io-client": "4.7.5",
"zustand": "4.4.7"
},
"peerDependencies": {
"@multiversx/sdk-core": ">= 13.0.0",
"@multiversx/sdk-dapp-utils": ">= 0.1.0",
"@multiversx/sdk-web-wallet-cross-window-provider": ">= 2.0.0-alpha.1",
"@multiversx/sdk-core": ">= 13.5.0",
"@multiversx/sdk-dapp-utils": ">= 1.0.1",
"@multiversx/sdk-web-wallet-cross-window-provider": ">= 2.0.1",
"axios": ">=1.6.5",
"bignumber.js": "9.x"
"bignumber.js": "9.x",
"immer": "10.x"
},
"resolutions": {
"string-width": "4.1.0"
},
"devDependencies": {
"@types/lodash": "4.17.4",
"@multiversx/sdk-core": ">= 13.0.0",
"@multiversx/sdk-dapp-utils": ">= 0.1.0",
"@multiversx/sdk-web-wallet-cross-window-provider": ">= 2.0.0-alpha.1",
"@multiversx/sdk-core": ">= 13.5.0",
"@multiversx/sdk-dapp-utils": "1.0.1",
"@multiversx/sdk-web-wallet-cross-window-provider": ">= 2.0.1",
"@swc/core": "^1.4.17",
"@swc/jest": "^0.2.36",
"@types/jest": "29.5.13",
"@types/lodash": "4.17.4",
"@types/node": "20.12.8",
"@typescript-eslint/eslint-plugin": "7.8.0",
"@typescript-eslint/parser": "7.8.0",
Expand All @@ -84,7 +87,6 @@
"msw": "1.3.1",
"node-stdlib-browser": "1.2.0",
"prettier": "3.2.5",
"protobufjs": "^7.3.0",
"react": "^18.3.1",
"rimraf": "^5.0.6",
"ts-jest": "29.1.2",
Expand Down
7 changes: 1 addition & 6 deletions src/core/methods/signMessage/signMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,14 @@ export interface SignMessageType {
};
}

// TODO: upgrade to Message
export const signMessage = async ({
message,
callbackRoute,
options
}: SignMessageType): Promise<Nullable<Message>> => {
const address = getAddress();
const provider = getAccountProvider();
const providerType = getProviderType(provider);

const callbackUrl = addOriginToLocationPath(callbackRoute);
const messageToSign = new Message({
address: new Address(address),
data: message.data
Expand All @@ -41,9 +38,7 @@ export const signMessage = async ({
}

// TODO upgrade sdk-dapp-utils to use Message as input for signMessage method and remove the cast
const signedMessage = await provider.signMessage(messageToSign as any, {
callbackUrl: encodeURIComponent(callbackUrl)
});
const signedMessage = await provider.signMessage(messageToSign, options);

// TODO upgrade sdk-dapp-utils to return Message instead of SignableMessage and remove the cast
return signedMessage as Nullable<Message>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Transaction } from '@multiversx/sdk-core/out';

export const getAreAllTransactionsSignedByGuardian = ({
transactions,
isGuarded
}: {
transactions: Transaction[];
isGuarded?: boolean;
}) => {
if (!isGuarded) {
return true;
}

if (transactions.length === 0) {
return false;
}

return transactions.every((tx) =>
Boolean(tx.getGuardianSignature().toString('hex'))
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Transaction } from '@multiversx/sdk-core';
import { getAreAllTransactionsSignedByGuardian } from './getAreAllTransactionsSignedByGuardian';
import { ProviderFactory } from 'core/providers/ProviderFactory';
import { getAccount } from 'core/methods/account/getAccount';
import { walletAddressSelector } from 'store/selectors';
import { getState } from 'store/store';

export const getGuardedTransactions = async ({
transactions
}: {
transactions: Transaction[];
}): Promise<Transaction[]> => {
const { isGuarded, address } = getAccount();
const walletAddress = walletAddressSelector(getState());

const allSignedByGuardian = getAreAllTransactionsSignedByGuardian({
isGuarded,
transactions
});

if (!isGuarded || allSignedByGuardian) {
return transactions;
}

const factory = new ProviderFactory();
const provider = await factory.createCrossWindowProvider({
address,
walletAddress
});
provider.setShouldShowConsentPopup(true);

const guardedTransactions = await provider.guardTransactions(transactions);

return guardedTransactions;
};
13 changes: 10 additions & 3 deletions src/core/methods/signTransactions/signTransactions.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {
Address,
Transaction,
TransactionOptions,
TransactionVersion
} from '@multiversx/sdk-core/out';
import { getAccountProvider } from 'core/providers';
import { getAccount } from '../account/getAccount';
import { getGuardedTransactions } from './helpers/getGuardedTransactions';

type SignTransactionsOptionsType = {
skipGuardian?: boolean;
Expand All @@ -15,21 +17,26 @@ export const signTransactions = async (
options: SignTransactionsOptionsType = {}
): Promise<Transaction[]> => {
const provider = getAccountProvider();
const { isGuarded } = getAccount();
const { isGuarded, activeGuardianAddress } = getAccount();

const transacitonsToSign =
isGuarded && !options.skipGuardian
activeGuardianAddress && !options.skipGuardian
? transactions?.map((transaction) => {
transaction.setVersion(TransactionVersion.withTxOptions());
transaction.setOptions(
TransactionOptions.withOptions({ guarded: true })
);
transaction.setGuardian(Address.fromBech32(activeGuardianAddress));
return transaction;
})
: transactions;

const signedTransactions: Transaction[] =
(await provider.signTransactions(transacitonsToSign)) ?? [];

return signedTransactions;
const guardedTransactions = isGuarded
? await getGuardedTransactions({ transactions: signedTransactions })
: signedTransactions;

return guardedTransactions;
};
120 changes: 110 additions & 10 deletions src/core/providers/ProviderFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import {
ProviderTypeEnum
} from './types/providerFactory.types';
import { isBrowserWithPopupConfirmation } from '../../constants';
import { fetchAccount } from 'utils';
import { setLedgerLogin } from 'store/actions/loginInfo/loginInfoActions';
import { setLedgerAccount } from 'store/actions/account/accountActions';
import { getLedgerProvider } from './helpers/getLedgerProvider';

export class ProviderFactory {
public async create({
Expand All @@ -17,14 +21,6 @@ export class ProviderFactory {
let createdProvider: IProvider | undefined;

switch (type) {
// case ProviderTypeEnum.iframe: {
// const provider = await ProviderFactory.getIframeProvider({
// walletAddress,
// });
// createdProvider = provider as unknown as IProvider;
// break;
// }

case ProviderTypeEnum.extension: {
const provider = await this.getExtensionProvider();
createdProvider = provider as unknown as IProvider;
Expand All @@ -39,7 +35,7 @@ export class ProviderFactory {
case ProviderTypeEnum.crossWindow: {
const { walletAddress } = config.network;

const provider = await this.getCrossWindowProvider({
const provider = await this.createCrossWindowProvider({
walletAddress,
address: config.account?.address || ''
});
Expand All @@ -52,6 +48,110 @@ export class ProviderFactory {
break;
}

case ProviderTypeEnum.ledger: {
const data = await getLedgerProvider();

if (!data) {
return;
}

const { ledgerProvider: provider, ledgerConfig } = data;

createdProvider = provider as unknown as IProvider;

const hwProviderLogin = provider.login;

createdProvider.getType = () => {
return ProviderTypeEnum.ledger;
};

createdProvider.login = async (options?: {
callbackUrl?: string | undefined;
token?: string | undefined;
}): Promise<{
address: string;
signature: string;
}> => {
const isConnected = provider.isConnected();

if (!isConnected) {
throw new Error('Ledger device is not connected');
}

// TODO: perform additional UI logic here
// maybe extract to file
const startIndex = 0;
const addressesPerPage = 10;

const accounts = await provider.getAccounts(
startIndex,
addressesPerPage
);

const accountsWithBalance: {
address: string;
balance: string;
index: number;
}[] = [];

const balancePromises = accounts.map((address) =>
fetchAccount(address)
);

const balances = await Promise.all(balancePromises);

balances.forEach((account, index) => {
if (!account) {
return;
}
accountsWithBalance.push({
address: account.address,
balance: account.balance,
index
});
});

// Suppose user selects the first account
const selectedIndex = 0;

setLedgerLogin({
index: selectedIndex,
loginType: ProviderTypeEnum.ledger
});

const { version, dataEnabled } = ledgerConfig;

setLedgerAccount({
address: accountsWithBalance[selectedIndex].address,
index: selectedIndex,
version,
hasContractDataEnabled: dataEnabled
});

if (options?.token) {
const loginInfo = await provider.tokenLogin({
token: Buffer.from(`${options?.token}{}`),
addressIndex: accountsWithBalance[selectedIndex].index
});

return {
address: loginInfo.address,
signature: loginInfo.signature.toString('hex')
};
} else {
const { address } = await hwProviderLogin({
addressIndex: accountsWithBalance[selectedIndex].index
});
return {
address,
signature: ''
};
}
};

break;
}

case ProviderTypeEnum.custom: {
createdProvider = customProvider;
break;
Expand All @@ -64,7 +164,7 @@ export class ProviderFactory {
return createdProvider;
}

private async getCrossWindowProvider({
public async createCrossWindowProvider({
address,
walletAddress
}: {
Expand Down
Loading
Loading