Skip to content

Commit

Permalink
Merge pull request #17 from cosmos/merge-main
Browse files Browse the repository at this point in the history
Merge into main
  • Loading branch information
schnetzlerjoe authored Sep 15, 2023
2 parents ec4ff89 + c62d097 commit ccec2f0
Show file tree
Hide file tree
Showing 38 changed files with 863 additions and 617 deletions.
2 changes: 1 addition & 1 deletion packages/snap/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cosmsnap/snap",
"version": "0.1.14",
"version": "0.1.15",
"description": "The Cosmos extension for your Metamask wallet.",
"repository": {
"type": "git",
Expand Down
4 changes: 2 additions & 2 deletions packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"version": "0.1.14",
"version": "0.1.15",
"description": "Cosmos Extension that adds Cosmos support to Metamask.",
"proposedName": "Cosmos Extension",
"repository": {
"type": "git",
"url": "https://github.com/cosmos/snap.git"
},
"source": {
"shasum": "OrlMarbyPhlqsFVD5Zm58D+uckRBrKujDszSqKIeAiE=",
"shasum": "9eVSJzLe5ny92sbfnBNnsq4Tgus0iU01NVTgpCDjeSo=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/snap/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const DEFAULT_FEES = {

export const DEFAULT_SLIP44 = 118;

export const WALLET_URL = "https://wallet.mysticlabs.xyz";
export const WALLET_URL = "https://metamask.mysticlabs.xyz";

// The multiplier to use to get u{denom} from {denom}
export const U_MULTIPLIER = 1000000;
Expand Down
115 changes: 96 additions & 19 deletions packages/snap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { COIN_TYPES, DEFAULT_FEES } from "./constants";
import { SignDoc, TxBody } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import { StdSignDoc } from "@cosmjs/amino";
import { decodeProtoMessage } from "./parser";
import Long from "long";
import { Key } from '@keplr-wallet/types';
import { fromBech32 } from '@cosmjs/encoding';

/**
* Handle incoming JSON-RPC requests, sent through `wallet_invokeSnap`.
Expand Down Expand Up @@ -244,7 +247,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
typeof request.params.chain_id == "string"
)
) {
throw new Error("Invalid transact request");
throw new Error("Invalid sendTx request");
}

let txBytes: Uint8Array = JSON.parse(request.params.tx)
Expand Down Expand Up @@ -306,22 +309,19 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
typeof request.params == "object" &&
"sign_doc" in request.params &&
"chain_id" in request.params &&
typeof request.params.chain_id == "string"
"signer" in request.params &&
typeof request.params.chain_id == "string" &&
typeof request.params.signer == "string"
)
) {
throw new Error("Invalid transact request");
}

let signer: string | null = null;
if (request.params.signer) {
if (typeof request.params.signer == "string") {
signer = request.params.signer
}
throw new Error("Invalid signDirect request");
}

let signDoc: SignDoc = request.params.sign_doc as unknown as SignDoc;
let {low, high, unsigned} = signDoc.accountNumber
let accountNumber = new Long(low, high, unsigned);
let signDocNew: SignDoc = {
accountNumber: signDoc.accountNumber,
accountNumber,
bodyBytes: new Uint8Array(Object.values(signDoc.bodyBytes)),
authInfoBytes: new Uint8Array(Object.values(signDoc.authInfoBytes)),
chainId: signDoc.chainId
Expand Down Expand Up @@ -362,10 +362,17 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
throw new Error("Transaction was denied.");
}

let newSignDoc: SignDoc = {
bodyBytes: new Uint8Array(Object.values(signDoc.bodyBytes)),
authInfoBytes: new Uint8Array(Object.values(signDoc.authInfoBytes)),
chainId: signDoc.chainId,
accountNumber: new Long(signDoc.accountNumber.low, signDoc.accountNumber.high, signDoc.accountNumber.unsigned)
}

let resultTx = await signDirect(
request.params.chain_id,
signer,
signDoc
request.params.signer,
newSignDoc
);

if (typeof resultTx === "undefined") {
Expand All @@ -389,10 +396,12 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
typeof request.params == "object" &&
"sign_doc" in request.params &&
"chain_id" in request.params &&
typeof request.params.chain_id == "string"
"signer" in request.params &&
typeof request.params.chain_id == "string" &&
typeof request.params.signer == "string"
)
) {
throw new Error("Invalid transact request");
throw new Error("Invalid signAmino request");
}

let signerAmino: string | null = null;
Expand Down Expand Up @@ -435,7 +444,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({

let resultTxAmino = await signAmino(
request.params.chain_id,
signerAmino,
request.params.signer,
signDocAmino
);

Expand Down Expand Up @@ -778,21 +787,89 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
typeof request.params.chain_id == "string"
)
) {
throw new Error("Invalid getChainAddress request");
throw new Error("Invalid getAccountInfo request");
}

let account: AccountData = await ChainState.GetAccount(request.params.chain_id);

return {
data: {
algo: account.algo.toString(),
algo: 'secp256k1',
address: account.address,
pubkey: new Uint8Array(Object.values(account.pubkey))
pubkey: new Uint8Array(Object.values(account.pubkey)),
},
success: true,
statusCode: 200,
};

case "getKey":
if (
!(
request.params != null &&
typeof request.params == "object" &&
"chain_id" in request.params &&
typeof request.params.chain_id == "string"
)
) {
throw new Error("Invalid getKey request");
}

let accountKey: AccountData = await ChainState.GetAccount(request.params.chain_id);
const bechInfo = fromBech32(accountKey.address);
const addressBytes = Uint8Array.from(bechInfo.data);

let key: Key = {
algo: 'secp256k1',
address: addressBytes,
pubKey: new Uint8Array(Object.values(accountKey.pubkey)),
bech32Address: accountKey.address,
name: "Cosmos MetaMask Extension",
isNanoLedger: false,
isKeystone: false
}

return {
data: key,
success: true,
statusCode: 200,
};

case "txAlert":

if (
!(
request.params != null &&
typeof request.params == "object" &&
"chain_id" in request.params &&
"hash" in request.params &&
typeof request.params.chain_id == "string" &&
typeof request.params.hash == "string"
)
) {
throw new Error("Invalid txAlert request");
}

let hash: string = request.params.hash;

await snap.request({
method: "snap_dialog",
params: {
type: "alert",
content: panel([
heading("Transaction Successful"),
text(
`Transaction with the hash ${hash} has been broadcasted to the chain ${request.params.chain_id}.`
),
copyable(`${hash}`),
]),
},
});
return {
data: {},
success: true,
statusCode: 200,
};

default:
throw new Error("Method not found.");
}
Expand Down
121 changes: 88 additions & 33 deletions packages/snap/src/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { DeliverTxResponse, SigningStargateClient } from "@cosmjs/stargate";
import { DirectSecp256k1Wallet, DirectSignResponse } from "@cosmjs/proto-signing";
import { Secp256k1Wallet, AminoSignResponse, StdSignDoc } from "@cosmjs/amino";
import { Fees } from "./types/chains";
import { encodeSecp256k1Signature, serializeSignDoc, rawSecp256k1PubkeyToRawAddress } from "@cosmjs/amino";
import { Secp256k1, sha256 } from "@cosmjs/crypto";
import { AccountData, DirectSecp256k1Wallet, DirectSignResponse, OfflineDirectSigner, makeSignBytes } from "@cosmjs/proto-signing";
import { AminoSignResponse, StdSignDoc } from "@cosmjs/amino";
import { toBech32 } from "@cosmjs/encoding";
import { Chain, Fees } from "./types/chains";
import { ChainState } from "./state";
import { heading, panel, text } from "@metamask/snaps-ui";
import {
Expand All @@ -11,6 +14,7 @@ import {
DEFAULT_AVG_GAS,
} from "./constants";
import { SignDoc } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import { getAddress } from "./address";

/**
* submitTransaction Submits a transaction to the chain specified.
Expand Down Expand Up @@ -192,9 +196,9 @@ export const sendTx = async (
*/
export const signDirect = async (
chain_id: string,
signer: string | null,
signer: string,
sign_doc: SignDoc
): Promise<DirectSignResponse | undefined> => {
): Promise<any> => {
try {
// get the chain from state
let chain = await ChainState.getChain(chain_id);
Expand All @@ -221,22 +225,17 @@ export const signDirect = async (
if (pk.startsWith("0x")) {
pk = pk.substring(2);
}
// create Buffer for pk
let bytesPK = new Uint8Array(Buffer.from(pk, 'hex'));

// create the wallet
let wallet = await DirectSecp256k1Wallet.fromKey(
Uint8Array.from(Buffer.from(pk, "hex")),
chain.bech32_prefix
);

// get current wallet user as signer if signer not provided
if (signer == null) {
signer = (await wallet.getAccounts())[0].address;
}
let wallet = await Wallet.fromKey(bytesPK, chain);

// sign directly
let tx = await wallet.signDirect(signer, sign_doc);
let response = await wallet.signDirect(signer, sign_doc);

return tx
return {
signed: { ...sign_doc, accountNumber: sign_doc.accountNumber.toString() },
signature: response.signature,
};
} catch (err: any) {
console.error("Error During SignDirect: ", err.message);
await snap.request({
Expand Down Expand Up @@ -264,7 +263,7 @@ export const signDirect = async (
*/
export const signAmino = async (
chain_id: string,
signer: string | null,
signer: string,
sign_doc: StdSignDoc
): Promise<AminoSignResponse | undefined> => {
try {
Expand Down Expand Up @@ -293,22 +292,14 @@ export const signAmino = async (
if (pk.startsWith("0x")) {
pk = pk.substring(2);
}
// create Buffer for pk
let bytesPK = new Uint8Array(Buffer.from(pk, 'hex'));

// create the wallet for Amino
let wallet = await Secp256k1Wallet.fromKey(
Uint8Array.from(Buffer.from(pk, "hex")),
chain.bech32_prefix
);
let wallet = await Wallet.fromKey(bytesPK, chain);

// get current wallet user as signer if signer not provided
if (signer == null) {
signer = (await wallet.getAccounts())[0].address;
}
let response = await wallet.signAmino(signer, sign_doc);

// Sign using Amino
const signedTx = await wallet.signAmino(signer, sign_doc); // Adjust this based on your library

return signedTx;
return response;
} catch (err: any) {
console.error("Error During signAmino: ", err.message);
await snap.request({
Expand All @@ -322,4 +313,68 @@ export const signAmino = async (
},
});
}
};
};

export class Wallet implements OfflineDirectSigner {
/**
* Creates a DirectSecp256k1Wallet from the given private key
*
* @param privkey The private key.
* @param prefix The bech32 address prefix (human readable part). Defaults to "cosmos".
*/
public static async fromKey(privkey: Uint8Array, chain: Chain): Promise<Wallet> {
const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey;
return new Wallet(privkey, Secp256k1.compressPubkey(uncompressed), chain);
}

private readonly pubkey: Uint8Array;
private readonly privkey: Uint8Array;
private readonly chain: Chain;

private constructor(privkey: Uint8Array, pubkey: Uint8Array, chain: Chain) {
this.privkey = privkey;
this.pubkey = pubkey;
this.chain = chain;
}

public async getAccounts(): Promise<readonly AccountData[]> {
let account = await getAddress(this.chain)
return [
{
algo: "secp256k1",
address: account,
pubkey: this.pubkey,
},
];
}

public async signDirect(address: string, signDoc: SignDoc): Promise<DirectSignResponse> {
const signBytes = makeSignBytes(signDoc);
let checkAddress = await getAddress(this.chain);
if (address !== checkAddress) {
throw new Error(`Address ${address} not found in wallet`);
}
const hashedMessage = sha256(signBytes);
const signature = await Secp256k1.createSignature(hashedMessage, this.privkey);
const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]);
const stdSignature = encodeSecp256k1Signature(this.pubkey, signatureBytes);
return {
signed: signDoc,
signature: stdSignature,
};
}

public async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise<AminoSignResponse> {
let checkAddress = await getAddress(this.chain);
if (signerAddress !== checkAddress) {
throw new Error(`Address ${signerAddress} not found in wallet`);
}
const message = sha256(serializeSignDoc(signDoc));
const signature = await Secp256k1.createSignature(message, this.privkey);
const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]);
return {
signed: signDoc,
signature: encodeSecp256k1Signature(this.pubkey, signatureBytes),
};
}
}
Loading

0 comments on commit ccec2f0

Please sign in to comment.