Skip to content

Commit

Permalink
Add Polkajs provider for Substrate (#108)
Browse files Browse the repository at this point in the history
* Feat: Providers are not handle by Substrate account

Solution: Add Polka.js support

* Fix: solve polkadot build issue according to this: polkadot-js/api#4633

* fix import

* update package-lock.json

* fix imports

* add jest-environment-jsdom

---------

Co-authored-by: mhh <[email protected]>
  • Loading branch information
Rgascoin and MHHukiewitz committed Jan 3, 2024
1 parent f4a01db commit 887e2cf
Show file tree
Hide file tree
Showing 12 changed files with 16,037 additions and 9,960 deletions.
1 change: 1 addition & 0 deletions examples/toolshed/src/components/SelectProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const availableKeypairs: Option[] = [
]

export const availableWallets: Option[] = [
{ label: 'PolkaDot (via Polka.js)', value: WalletChains.Substrate },
{ label: 'Ethereum (via Metamask)', value: WalletChains.Ethereum },
{ label: 'Solana (via Phantom)', value: WalletChains.Solana },
]
Expand Down
3 changes: 2 additions & 1 deletion examples/toolshed/src/components/WalletConfig.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { avalanche, ethereum, solana } from '../../../../src/accounts'
import { solana, ethereum, avalanche, substrate } from '../../../../src/accounts'
import { WalletChains } from '../model/chains'
import { dispatchAndConsume } from '../model/componentProps'
import { Actions } from '../reducer'
Expand All @@ -24,6 +24,7 @@ function WalletConfig({ dispatch, state } : dispatchAndConsume) {
const [customEndpoint, setCustomEndpoint] = useState<RpcChainType>(availableChains[0].value)
const getAccountClass = () => (
state.selectedChain === WalletChains.Avalanche ? [avalanche, window.ethereum]
: state.selectedChain === WalletChains.Substrate ? [substrate, null]
: state.selectedChain === WalletChains.Ethereum ? [ethereum, window.ethereum]
: state.selectedChain === WalletChains.Solana ? [solana, window.phantom?.solana]
: [null, null]
Expand Down
1 change: 1 addition & 0 deletions examples/toolshed/src/model/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum HardwareChains {

export enum WalletChains {
Avalanche = "AVAX",
Substrate = "DOT",
Ethereum = "ETH",
Solana = "SOL",
}
Expand Down
25,881 changes: 15,949 additions & 9,932 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"eslint-plugin-prettier": "^4.0.0",
"formdata-node": "^6.0.3",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"prettier": "^2.6.2",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
Expand All @@ -77,6 +78,8 @@
"@ledgerhq/hw-transport-node-hid": "^6.27.6",
"@ledgerhq/hw-transport-webusb": "^6.27.6",
"@metamask/eth-sig-util": "^5.0.0",
"@polkadot/extension-dapp": "^0.44.6",
"@polkadot/extension-inject": "^0.44.6",
"@polkadot/keyring": "^7.7.1",
"@polkadot/util": "^7.7.1",
"@polkadot/util-crypto": "^7.7.1",
Expand Down
96 changes: 75 additions & 21 deletions src/accounts/substrate.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import { Account } from "./account";
import "@polkadot/api-augment";

import { BaseMessage, Chain } from "../messages/types";
import { GetVerificationBuffer } from "../messages";

import { InjectedExtension } from "@polkadot/extension-inject/types";
import { Keyring } from "@polkadot/keyring";
import { KeyringPair } from "@polkadot/keyring/types";
import { cryptoWaitReady } from "@polkadot/util-crypto";
import { cryptoWaitReady, signatureVerify } from "@polkadot/util-crypto";
import { generateMnemonic } from "@polkadot/util-crypto/mnemonic/bip39";
import { stringToHex } from "@polkadot/util";

/**
* DOTAccount implements the Account class for the substrate protocol.
* It is used to represent a substrate account when publishing a message on the Aleph network.
*/
export class DOTAccount extends Account {
private pair: KeyringPair;
constructor(pair: KeyringPair) {
super(pair.address);
this.pair = pair;
private pair?: KeyringPair;
private injector?: InjectedExtension;

constructor(pair: KeyringPair | InjectedExtension, address: string) {
super(address);

if ("address" in pair) {
this.pair = pair;
} else {
this.injector = pair;
}
}

GetChain(): Chain {
Expand All @@ -30,17 +40,29 @@ export class DOTAccount extends Account {
*
* @param message The Aleph message to sign, using some of its fields.
*/
Sign(message: BaseMessage): Promise<string> {
async Sign(message: BaseMessage): Promise<string> {
const buffer = GetVerificationBuffer(message);
return new Promise((resolve) => {
const signed = `0x${Buffer.from(this.pair.sign(buffer)).toString("hex")}`;

resolve(
JSON.stringify({
curve: "sr25519",
data: signed,
}),
);
let signed = "";

if (this.pair) {
signed = `0x${Buffer.from(this.pair.sign(buffer)).toString("hex")}`;
} else {
const signRaw = this.injector?.signer?.signRaw;
if (signRaw) {
const { signature } = await signRaw({
address: this.address,
data: stringToHex(buffer.toString()),
type: "bytes",
});
signed = signature;
}
}

if (!signatureVerify(buffer, signed, this.address).isValid) throw new Error("Data can't be signed.");

return JSON.stringify({
curve: "sr25519",
data: signed,
});
}

Expand All @@ -50,7 +72,8 @@ export class DOTAccount extends Account {
* @param content The content to encrypt.
*/
encrypt(content: Buffer): Buffer {
return Buffer.from(this.pair.encryptMessage(content, this.pair.address));
if (this.pair) return Buffer.from(this.pair.encryptMessage(content, this.pair.address));
throw "Error: Can not encrypt";
}

/**
Expand All @@ -59,8 +82,10 @@ export class DOTAccount extends Account {
* @param encryptedContent The encrypted content to decrypt.
*/
decrypt(encryptedContent: Buffer): Buffer | null {
const res = this.pair.decryptMessage(encryptedContent, this.pair.address);
if (res) return Buffer.from(res);
if (this.pair) {
const res = this.pair.decryptMessage(encryptedContent, this.pair.address);
if (res) return Buffer.from(res);
}

throw "Error: This message can't be decoded";
}
Expand All @@ -86,7 +111,8 @@ export async function ImportAccountFromMnemonic(mnemonic: string): Promise<DOTAc
const keyRing = new Keyring({ type: "sr25519" });

await cryptoWaitReady();
return new DOTAccount(keyRing.createFromUri(mnemonic, { name: "sr25519" }));
const keyRingPair = keyRing.createFromUri(mnemonic, { name: "sr25519" });
return new DOTAccount(keyRingPair, keyRingPair.address);
}

/**
Expand All @@ -100,5 +126,33 @@ export async function ImportAccountFromPrivateKey(privateKey: string): Promise<D
const keyRing = new Keyring({ type: "sr25519" });

await cryptoWaitReady();
return new DOTAccount(keyRing.createFromUri(privateKey, { name: "sr25519" }));
const keyRingPair = keyRing.createFromUri(privateKey, { name: "sr25519" });
return new DOTAccount(keyRingPair, keyRingPair.address);
}

/**
* Get an account from polkadot.js provider
* This function can only be called inside a browser.
* @param {string} address that can refer an account to connect, by default connect account number 0
*/
export async function GetAccountFromProvider(address?: string): Promise<DOTAccount> {
let web3Bundle: typeof import("@polkadot/extension-dapp");

try {
web3Bundle = await import("@polkadot/extension-dapp");
} catch (e: any) {
throw new Error("Substrate provider can only be instanced in the browser.");
}

const extensions = await web3Bundle.web3Enable("Aleph Ts-Sdk");
let injector: InjectedExtension;

if (extensions.length === 0) {
throw new Error("Error: No provider installed");
}

const allAccounts = await web3Bundle.web3Accounts();
if (address) injector = await web3Bundle.web3FromAddress(address);
else injector = await web3Bundle.web3FromAddress(allAccounts[0].address);
return new DOTAccount(injector, (await injector.accounts.get())[0].address);
}
2 changes: 1 addition & 1 deletion src/messages/any/getMessagesSocket.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DEFAULT_API_WS_V2 } from "../../global";
import { Chain, HashType, ItemType, MessageType, BaseContent } from "../message";
import { Chain, HashType, ItemType, MessageType, BaseContent } from "../types";
import { AlephWebSocket } from "./AlephWebSocket";
import { isNode } from "../../utils/env";
import { AlephNodeWebSocket } from "./AlephNodeWebSocket";
Expand Down
2 changes: 1 addition & 1 deletion tests/accounts/cosmos.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { cosmos, post } from "../index";
import { DEFAULT_API_V2 } from "../../src/global";
import { ItemType } from "../../src/messages/message";
import { ItemType } from "../../src/messages/types";
import { EphAccountList } from "../testAccount/entryPoint";
import fs from "fs";

Expand Down
2 changes: 1 addition & 1 deletion tests/accounts/ethereum.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as bip39 from "bip39";
import { ethereum } from "../index";
import { ethers } from "ethers";
import { EthereumProvider } from "../providers/ethereumProvider";
import { MessageType, ItemType } from "../../src/messages/message";
import { MessageType, ItemType } from "../../src/messages/types";
import { EphAccountList } from "../testAccount/entryPoint";
import fs from "fs";

Expand Down
2 changes: 1 addition & 1 deletion tests/accounts/solana.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ItemType, MessageType } from "../../src/messages/message";
import { ItemType, MessageType } from "../../src/messages/types";
import { post, solana } from "../index";
import { Keypair } from "@solana/web3.js";
import { panthomLikeProvider, officialLikeProvider } from "../providers/solanaProvider";
Expand Down
2 changes: 1 addition & 1 deletion tests/accounts/tezos.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @jest-environment jsdom
*/
import { ItemType } from "../../src/messages/message";
import { ItemType } from "../../src/messages/types";
import { post, tezos } from "../index";
import { DEFAULT_API_V2 } from "../../src/global";
import { b58cencode, prefix, validateSignature } from "@taquito/utils";
Expand Down
2 changes: 1 addition & 1 deletion tests/providers/ethereumProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { personalSign } from "@metamask/eth-sig-util";
import { getEncryptionPublicKey, decrypt } from "@metamask/eth-sig-util/dist/encryption";
import { getEncryptionPublicKey, decrypt } from "@metamask/eth-sig-util";

type ProviderSetup = {
address: string;
Expand Down

0 comments on commit 887e2cf

Please sign in to comment.