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

Add Polkajs provider for Substrate #108

Merged
merged 6 commits into from
Jan 3, 2024
Merged
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 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
Loading