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

feat: add Injective support #27

Merged
merged 7 commits into from
Dec 9, 2023
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## `v0.0.42`

### Features

- Added support for Injective

## `v0.0.40`

### Fixes
Expand Down
11 changes: 9 additions & 2 deletions examples/solid-vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const CHAINS: Record<string, string> = {
"columbus-5": "Terra Classic",
"neutron-1": "Neutron",
"migaloo-1": "Migaloo",
"injective-1": "Injective",
};
const WALLETS: Record<WalletName, string> = {
[WalletName.KEPLR]: "Keplr",
Expand Down Expand Up @@ -61,6 +62,8 @@ function getRpc(chain: string): string {
return "https://neutron-rpc.polkachu.com";
case "migaloo-1":
return "https://migaloo-rpc.polkachu.com";
case "injective-1":
return "https://injective-rpc.polkachu.com";
default:
throw new Error("Unknown chain");
}
Expand All @@ -82,6 +85,8 @@ function getGasPrice(chain: string): { amount: string; denom: string } {
return { amount: "0.01", denom: getDenom(chain) };
case "migaloo-1":
return { amount: "0.25", denom: getDenom(chain) };
case "injective-1":
return { amount: "500000000", denom: getDenom(chain) };
default:
throw new Error("Unknown chain");
}
Expand All @@ -102,14 +107,16 @@ function getDenom(chain: string): string {
return "untrn";
case "migaloo-1":
return "uwhale";
case "injective-1":
return "inj";
default:
throw new Error("Unknown chain");
}
}

const App: Component = () => {
const [chain, setChain] = createSignal<string>("migaloo-1");
const [wallet, setWallet] = createSignal<WalletName>(WalletName.STATION);
const [chain, setChain] = createSignal<string>("injective-1");
const [wallet, setWallet] = createSignal<WalletName>(WalletName.KEPLR);
const [wallets, setWallets] = createStore<Record<string, ConnectedWallet>>(
{}
);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cosmes",
"version": "0.0.40",
"version": "0.0.42",
"private": false,
"packageManager": "[email protected]",
"sideEffects": false,
Expand Down
150 changes: 88 additions & 62 deletions scripts/gen-protobufs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,82 +59,108 @@ const TMP_DIR = join(PROTOBUFS_DIR, ".tmp");
const id = (/** @type {string} */ repo) => repo.replace(/[#/]/g, "-");

console.log("Initialising directories...");
rmSync(PROTOBUFS_DIR, { recursive: true, force: true });
rmSync(TMP_DIR, { recursive: true, force: true });
mkdirSync(PROTOBUFS_DIR);
mkdirSync(TMP_DIR);
{
rmSync(PROTOBUFS_DIR, { recursive: true, force: true });
rmSync(TMP_DIR, { recursive: true, force: true });
mkdirSync(PROTOBUFS_DIR);
mkdirSync(TMP_DIR);
}

console.log("Cloning required repos...");
await Promise.all(
REPOS.map(({ repo }) => degit(repo).clone(join(TMP_DIR, id(repo))))
);
{
await Promise.all(
REPOS.map(({ repo }) => degit(repo).clone(join(TMP_DIR, id(repo))))
);
}

console.log("Generating TS files from proto files...");
for (const { repo, paths } of REPOS) {
for (const path of paths) {
spawnSync("pnpm", ["buf", "generate", join(TMP_DIR, id(repo), path)], {
cwd: process.cwd(),
stdio: "inherit",
});
{
for (const { repo, paths } of REPOS) {
for (const path of paths) {
spawnSync("pnpm", ["buf", "generate", join(TMP_DIR, id(repo), path)], {
cwd: process.cwd(),
stdio: "inherit",
});
}
console.log(`✔️ [${repo}]`);
}
console.log(`✔️ [${repo}]`);
}

console.log("Generating src/index.ts file and renaming exports...");
const LAST_SEGMENT_REGEX = /[^/]+$/;
const EXPORTED_NAME_REGEX = /^export \w+ (\w+) /gm;
let contents =
"/** This file is generated by gen-protobufs.mjs. Do not edit. */\n\n";
/**
* Builds the `src/proto/index.ts` file to re-export generated code.
* A prefix is added to the exported names to avoid name collisions.
* The prefix is the names of the directories in `proto` leading up
* to the directory of the exported code, concatenated in PascalCase.
* For example, if the exported code is in `proto/foo/bar/goo.ts`, the
* prefix will be `FooBar`.
* @param {string} dir
*/
function generateIndexExports(dir) {
const files = globSync(join(dir, "*"));
if (files.length === 0) {
return;
}
const prefixName = dir
.replace(PROTOBUFS_DIR + "/", "")
.split("/")
.map((name) =>
// convert all names to PascalCase
name.split(/[-_]/).map(capitalize).join("")
)
.join("");
for (const file of files) {
const fileName = file.match(LAST_SEGMENT_REGEX)?.[0];
if (!fileName) {
console.error("Could not find name for", file);
continue;
{
const LAST_SEGMENT_REGEX = /[^/]+$/;
const EXPORTED_NAME_REGEX = /^export \w+ (\w+) /gm;
let contents =
"/** This file is generated by gen-protobufs.mjs. Do not edit. */\n\n";
/**
* Builds the `src/proto/index.ts` file to re-export generated code.
* A prefix is added to the exported names to avoid name collisions.
* The prefix is the names of the directories in `proto` leading up
* to the directory of the exported code, concatenated in PascalCase.
* For example, if the exported code is in `proto/foo/bar/goo.ts`, the
* prefix will be `FooBar`.
* @param {string} dir
*/
function generateIndexExports(dir) {
const files = globSync(join(dir, "*"));
if (files.length === 0) {
return;
}
if (!fileName.endsWith(".ts")) {
continue;
const prefixName = dir
.replace(PROTOBUFS_DIR + "/", "")
.split("/")
.map((name) =>
// convert all names to PascalCase
name.split(/[-_]/).map(capitalize).join("")
)
.join("");
for (const file of files) {
const fileName = file.match(LAST_SEGMENT_REGEX)?.[0];
if (!fileName) {
console.error("Could not find name for", file);
continue;
}
if (!fileName.endsWith(".ts")) {
continue;
}
const code = readFileSync(file, "utf8");
contents += `export {\n`;
for (const match of code.matchAll(EXPORTED_NAME_REGEX)) {
const exportedName = match[1];
contents += ` ${exportedName} as ${prefixName + exportedName},\n`;
}
const exportedFile = file
.replace(PROTOBUFS_DIR + "/", "")
.replace(".ts", ".js");
contents += `} from "./${exportedFile}";\n`;
}
const code = readFileSync(file, "utf8");
contents += `export {\n`;
for (const match of code.matchAll(EXPORTED_NAME_REGEX)) {
const exportedName = match[1];
contents += ` ${exportedName} as ${prefixName + exportedName},\n`;
for (const file of files) {
generateIndexExports(file);
}
const exportedFile = file
.replace(PROTOBUFS_DIR + "/", "")
.replace(".ts", ".js");
contents += `} from "./${exportedFile}";\n`;
}
for (const file of files) {
generateIndexExports(file);
}
generateIndexExports(PROTOBUFS_DIR);
writeFileSync(join(PROTOBUFS_DIR, "index.ts"), contents);
}

console.log("Rewriting Injective's legacy CosmWasm dependencies...");
{
const path = join(
PROTOBUFS_DIR,
"injective",
"wasmx",
"v1",
"proposal_pb.ts"
);
const contents = readFileSync(path, "utf8").replace(
"proposal_pb.js",
"proposal_legacy_pb.js"
);
writeFileSync(path, contents);
}
generateIndexExports(PROTOBUFS_DIR);
writeFileSync(join(PROTOBUFS_DIR, "index.ts"), contents);

console.log("Cleaning up...");
rmSync(TMP_DIR, { recursive: true, force: true });
{
rmSync(TMP_DIR, { recursive: true, force: true });
}

console.log("Proto generation completed successfully!");
11 changes: 8 additions & 3 deletions src/client/models/Secp256k1PubKey.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { PlainMessage } from "@bufbuild/protobuf";
import { base64 } from "cosmes/codec";
import { CosmosCryptoSecp256k1PubKey as ProtoSecp256k1PubKey } from "cosmes/protobufs";
import {
InjectiveCryptoV1beta1Ethsecp256k1PubKey as ProtoInjectiveSecp256k1PubKey,
CosmosCryptoSecp256k1PubKey as ProtoSecp256k1PubKey,
} from "cosmes/protobufs";

import { DeepPrettify } from "../../typeutils/prettify";
import { Adapter } from "./Adapter";
Expand All @@ -14,8 +17,10 @@ export class Secp256k1PubKey implements Adapter {
this.data = data;
}

public toProto() {
return new ProtoSecp256k1PubKey(this.data);
public toProto(isInjective = false) {
return isInjective
? new ProtoInjectiveSecp256k1PubKey(this.data)
: new ProtoSecp256k1PubKey(this.data);
}

public toAmino() {
Expand Down
70 changes: 36 additions & 34 deletions src/client/models/Tx.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { PlainMessage } from "@bufbuild/protobuf";
import {
CosmosTxV1beta1AuthInfo as ProtoAuthInfo,
CosmosTxV1beta1Fee as ProtoFee,
CosmosTxSigningV1beta1SignMode as ProtoSignMode,
CosmosTxV1beta1SignerInfo as ProtoSignerInfo,
CosmosTxV1beta1TxBody as ProtoTxBody,
CosmosTxV1beta1TxRaw as ProtoTxRaw,
CosmosTxV1beta1SignDoc as SignDoc,
Expand All @@ -10,9 +12,11 @@ import { StdSignDoc } from "cosmes/registry";

import { toAny } from "../utils/toAny";
import { Adapter } from "./Adapter";
import { Secp256k1PubKey } from "./Secp256k1PubKey";

type Data = {
pubKey: Adapter;
chainId: string;
pubKey: Secp256k1PubKey;
msgs: Adapter[];
};

Expand All @@ -30,7 +34,6 @@ export type ToUnsignedProtoParams = Pick<
>;

export type ToSignDocParams = {
chainId: string;
accountNumber: bigint;
sequence: bigint;
fee: ProtoFee;
Expand Down Expand Up @@ -60,20 +63,7 @@ export class Tx {
return new ProtoTxRaw({
authInfoBytes: new ProtoAuthInfo({
fee: fee,
signerInfos: [
{
publicKey: toAny(this.data.pubKey.toProto()),
sequence: sequence,
modeInfo: {
sum: {
case: "single",
value: {
mode: signMode,
},
},
},
},
],
signerInfos: [this.getSignerInfo(sequence, signMode)],
}).toBinary(),
bodyBytes: new ProtoTxBody({
messages: this.data.msgs.map((m) => toAny(m.toProto())),
Expand All @@ -100,31 +90,17 @@ export class Tx {
* Returns the unsigned, proto encoded tx ready to be signed by a wallet.
*/
public toSignDoc({
chainId,
accountNumber,
sequence,
fee,
memo,
}: ToSignDocParams): SignDoc {
return new SignDoc({
chainId: chainId,
chainId: this.data.chainId,
accountNumber: accountNumber,
authInfoBytes: new ProtoAuthInfo({
fee: fee,
signerInfos: [
{
publicKey: toAny(this.data.pubKey.toProto()),
sequence: sequence,
modeInfo: {
sum: {
case: "single",
value: {
mode: ProtoSignMode.DIRECT,
},
},
},
},
],
signerInfos: [this.getSignerInfo(sequence, ProtoSignMode.DIRECT)],
}).toBinary(),
bodyBytes: new ProtoTxBody({
messages: this.data.msgs.map((m) => toAny(m.toProto())),
Expand All @@ -137,14 +113,13 @@ export class Tx {
* Returns the unsigned, amino encoded tx ready to be signed by a wallet.
*/
public toStdSignDoc({
chainId,
accountNumber,
sequence,
fee,
memo,
}: ToStdSignDocParams): StdSignDoc {
return {
chain_id: chainId,
chain_id: this.data.chainId,
account_number: accountNumber.toString(),
sequence: sequence.toString(),
fee: {
Expand All @@ -155,4 +130,31 @@ export class Tx {
memo: memo ?? "",
};
}

/**
* Returns the signer info. The chain ID is used to determine if the public key
* should be encoded using Injective's custom protobuf.
*
* **Warning**: Injective's chain ID might change, causing potential issues here.
*/
private getSignerInfo(
sequence: bigint,
mode: ProtoSignMode
): PlainMessage<ProtoSignerInfo> {
return {
publicKey: toAny(
// TODO: Injective's chain ID might change in the future
this.data.pubKey.toProto(this.data.chainId.startsWith("injective-"))
),
sequence: sequence,
modeInfo: {
sum: {
case: "single",
value: {
mode: mode,
},
},
},
};
}
}
Loading
Loading