Skip to content

Commit

Permalink
Feat: Providers are not handle by Substrate account
Browse files Browse the repository at this point in the history
Solution: Add Polka.js support
  • Loading branch information
Rgascoin committed Dec 12, 2022
1 parent a2c6ba5 commit 432779a
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 21 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 @@ -22,6 +22,7 @@ export const availableKeypairs: Option[] = [

export const availableWallets: Option[] = [
{ label: 'Avalanche (via Metamask)', value: WalletChains.Avalanche },
{ 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 { solana, ethereum, avalanche } 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 @@ -7,6 +7,7 @@ import { Actions } from '../reducer'
function WalletConfig({ dispatch, state } : dispatchAndConsume) {
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"@ledgerhq/hw-app-eth": "^6.29.9",
"@ledgerhq/hw-transport-node-hid": "^6.27.6",
"@ledgerhq/hw-transport-webusb": "^6.27.6",
"@polkadot/extension-dapp": "^0.44.6",
"@polkadot/keyring": "^7.7.1",
"@polkadot/util": "^7.7.1",
"@polkadot/util-crypto": "^7.7.1",
Expand Down
94 changes: 74 additions & 20 deletions src/accounts/substrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,30 @@ import { Account } from "./account";
import { BaseMessage, Chain } from "../messages/message";
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);
}

0 comments on commit 432779a

Please sign in to comment.