Skip to content

Commit

Permalink
Merge pull request #91 from hyperweb-io/add-utils
Browse files Browse the repository at this point in the history
Add stargate utils
  • Loading branch information
Zetazzz authored Feb 17, 2025
2 parents 5939608 + 06a62f1 commit 6c86815
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/encoding/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"lint": "eslint . --fix"
},
"dependencies": {
"@interchainjs/math": "1.9.11",
"base64-js": "^1.3.0",
"bech32": "^1.1.4",
"readonly-date": "^1.0.0"
Expand Down
1 change: 1 addition & 0 deletions packages/encoding/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { fromBech32, normalizeBech32, toBech32 } from "./bech32";
export { fromHex, toHex } from "./hex";
export { fromRfc3339, toRfc3339 } from "./rfc3339";
export { fromUtf8, toUtf8 } from "./utf8";
export { toAccAddress, longify, decodeCosmosSdkDecFromProto } from "./utils";
21 changes: 21 additions & 0 deletions packages/encoding/src/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { fromHex } from "./hex";

import { decodeCosmosSdkDecFromProto } from "./utils";

describe("utils", () => {
describe("decodeCosmosSdkDecFromProto", () => {
it("works for string inputs", () => {
expect(decodeCosmosSdkDecFromProto("0").toString()).toEqual("0");
expect(decodeCosmosSdkDecFromProto("1").toString()).toEqual("0.000000000000000001");
expect(decodeCosmosSdkDecFromProto("3000000").toString()).toEqual("0.000000000003");
expect(decodeCosmosSdkDecFromProto("123456789123456789").toString()).toEqual("0.123456789123456789");
expect(decodeCosmosSdkDecFromProto("1234567891234567890").toString()).toEqual("1.23456789123456789");
});

it("works for byte inputs", () => {
expect(decodeCosmosSdkDecFromProto(fromHex("313330303033343138373830313631333938")).toString()).toEqual(
"0.130003418780161398",
);
});
});
});
32 changes: 32 additions & 0 deletions packages/encoding/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { fromAscii } from "./ascii";
import { fromBech32 } from "./bech32";
import { Decimal, Uint64 } from "@interchainjs/math";

/**
* Takes a bech32 encoded address and returns the data part. The prefix is ignored and discarded.
* This is called AccAddress in Cosmos SDK, which is basically an alias for raw binary data.
* The result is typically 20 bytes long but not restricted to that.
*/
export function toAccAddress(address: string): Uint8Array {
return fromBech32(address).data;
}

/**
* Takes a uint64 value as string, number, BigInt or Uint64 and returns a BigInt
* of it.
*/
export function longify(value: string | number | Uint64): bigint {
const checkedValue = Uint64.fromString(value.toString());
return BigInt(checkedValue.toString());
}

/**
* Takes a string or binary encoded `github.com/cosmos/cosmos-sdk/types.Dec` from the
* protobuf API and converts it into a `Decimal` with 18 fractional digits.
*
* See https://github.com/cosmos/cosmos-sdk/issues/10863 for more context why this is needed.
*/
export function decodeCosmosSdkDecFromProto(input: string | Uint8Array): Decimal {
const asString = typeof input === "string" ? input : fromAscii(input);
return Decimal.fromAtomics(asString, 18);
}
2 changes: 2 additions & 0 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"lint": "eslint . --fix"
},
"dependencies": {
"@chain-registry/v2": "1.71.71",
"@chain-registry/v2-types": "0.53.72",
"@interchainjs/types": "1.9.11",
"bech32": "^2.0.0",
"decimal.js": "^10.4.3"
Expand Down
27 changes: 27 additions & 0 deletions packages/utils/src/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* An event attribute.
*
* This is the same attribute type as tendermint34.Attribute and tendermint35.EventAttribute
* but `key` and `value` are unified to strings. The conversion
* from bytes to string in the Tendermint 0.34 case should be done by performing
* [lossy] UTF-8 decoding.
*
* [lossy]: https://doc.rust-lang.org/stable/std/string/struct.String.html#method.from_utf8_lossy
*/
export interface Attribute {
readonly key: string;
readonly value: string;
}

/**
* The same event type as tendermint34.Event and tendermint35.Event
* but attribute keys and values are unified to strings. The conversion
* from bytes to string in the Tendermint 0.34 case should be done by performing
* [lossy] UTF-8 decoding.
*
* [lossy]: https://doc.rust-lang.org/stable/std/string/struct.String.html#method.from_utf8_lossy
*/
export interface Event {
readonly type: string;
readonly attributes: readonly Attribute[];
}
2 changes: 2 additions & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export * from "./arrays";
export * from "./typechecks";
export * from "./chain";
export * from "./rpc";
export * from "./logs";
export * from "./events";
86 changes: 86 additions & 0 deletions packages/utils/src/logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { isObjectLike } from "./typechecks";

import { Attribute, Event } from "./events";

export interface Log {
readonly msg_index: number;
readonly log: string;
readonly events: readonly Event[];
}

export function parseAttribute(input: unknown): Attribute {
if (!isObjectLike(input)) throw new Error("Attribute must be a non-null object");
const { key, value } = input as any;
if (typeof key !== "string" || !key) throw new Error("Attribute's key must be a non-empty string");
if (typeof value !== "string" && typeof value !== "undefined") {
throw new Error("Attribute's value must be a string or unset");
}

return {
key: key,
value: value || "",
};
}

export function parseEvent(input: unknown): Event {
if (!isObjectLike(input)) throw new Error("Event must be a non-null object");
const { type, attributes } = input as any;
if (typeof type !== "string" || type === "") {
throw new Error(`Event type must be a non-empty string`);
}
if (!Array.isArray(attributes)) throw new Error("Event's attributes must be an array");
return {
type: type,
attributes: attributes.map(parseAttribute),
};
}

export function parseLog(input: unknown): Log {
if (!isObjectLike(input)) throw new Error("Log must be a non-null object");
const { msg_index, log, events } = input as any;
if (typeof msg_index !== "number") throw new Error("Log's msg_index must be a number");
if (typeof log !== "string") throw new Error("Log's log must be a string");
if (!Array.isArray(events)) throw new Error("Log's events must be an array");
return {
msg_index: msg_index,
log: log,
events: events.map(parseEvent),
};
}

export function parseLogs(input: unknown): readonly Log[] {
if (!Array.isArray(input)) throw new Error("Logs must be an array");
return input.map(parseLog);
}

export function parseRawLog(input: string | undefined): readonly Log[] {
// Cosmos SDK >= 0.50 gives us an empty string here. This should be handled like undefined.
if (!input) return [];

const logsToParse = JSON.parse(input).map(({ events }: { events: readonly unknown[] }, i: number) => ({
msg_index: i,
events,
log: "",
}));
return parseLogs(logsToParse);
}

/**
* Searches in logs for the first event of the given event type and in that event
* for the first first attribute with the given attribute key.
*
* Throws if the attribute was not found.
*/
export function findAttribute(logs: readonly Log[], eventType: string, attrKey: string): Attribute {
const firstLogs = logs.find(() => true);
const out = firstLogs?.events
.find((event) => event.type === eventType)
?.attributes.find((attr) => attr.key === attrKey);
if (!out) {
throw new Error(
`Could not find attribute '${attrKey}' in first event of type '${eventType}' in first log.`,
);
}
return out;
}
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1116,6 +1116,18 @@
resolved "https://registry.yarnpkg.com/@chain-registry/v2-types/-/v2-types-0.53.68.tgz#83173c3e79c7c89a382c4017e15db71970038847"
integrity sha512-MCK9RKJ67VYWCJ0HtnDYJIottx4paoo6wKMbH3s6xWFzLVwufJWi0iKMoppVOIXWk+yr+h9W3puaLJPnYNez9A==

"@chain-registry/[email protected]", "@chain-registry/v2-types@^0.53.40":
version "0.53.72"
resolved "https://registry.yarnpkg.com/@chain-registry/v2-types/-/v2-types-0.53.72.tgz#3621cc1e94cacb430c657c2af63d4825950b880f"
integrity sha512-HIbDFK0R1aZTbXdTN7FOZI+z3lHt4ZQWRYDctUk3IwvGxOC04gYCa45SfsHx4YpqALXa0hu9Acj5fUSp4mnZ5w==

"@chain-registry/[email protected]":
version "1.71.71"
resolved "https://registry.yarnpkg.com/@chain-registry/v2/-/v2-1.71.71.tgz#648eab79a487a2680c77b85fab96d5c5d5fb3eea"
integrity sha512-JdzJHRduw58io8ZOKy7mnAt9oFJqWSa5Bn5srWXG5326ztUCe1MLd9yZMMCos57mc4UFrOJm8Gr9V7lKtEZvjQ==
dependencies:
"@chain-registry/v2-types" "^0.53.40"

"@chain-registry/v2@^1.65.6":
version "1.71.121"
resolved "https://registry.yarnpkg.com/@chain-registry/v2/-/v2-1.71.121.tgz#38baa88c2d6eb59e8996a0bc2b754b2f492468d9"
Expand Down

0 comments on commit 6c86815

Please sign in to comment.