Skip to content

Commit

Permalink
keychain for wallet, rpc client, new wasm query to query all raw states
Browse files Browse the repository at this point in the history
  • Loading branch information
SlayerAnsh committed Oct 11, 2024
1 parent 292b7aa commit f77ddd1
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 70 deletions.
41 changes: 30 additions & 11 deletions packages/andrjs/src/AndromedaClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import type { ChainClient } from "./clients";
import createClient from "./clients";
import type { Fee, Msg } from "./types";
import ADOSchemaAPI from "api/ADOSchemaAPI";
import { HttpClient, RpcClient } from "@cosmjs/tendermint-rpc";
import { RpcClient } from "@cosmjs/tendermint-rpc";
import { PageRequest } from "cosmjs-types/cosmos/base/query/v1beta1/pagination";

/**
* A helper class for interacting with the Andromeda ecosystem
Expand Down Expand Up @@ -58,24 +59,18 @@ export default class AndromedaClient {
* @param options Any additional client options (**Only for CosmosClients**)
*/
async connect(
endpoint: string,
endpoint: string | RpcClient,
kernelAddress: string,
addressPrefix: string,
signer?: OfflineSigner | OfflineDirectSigner,
// Only used for Cosmos Clients
options?: SigningStargateClientOptions,
config?: Partial<ChainClient['config']>,
rpcClient?: RpcClient
) {
delete this.chainClient;

this.chainClient = createClient(addressPrefix, config);

// Nibiru rpc somehow doesn't work with HttpBatchClient
if (!rpcClient && addressPrefix === 'nibi') {
rpcClient = new HttpClient(endpoint);
}
await this.chainClient.connect(endpoint, signer, options, rpcClient);
await this.chainClient.connect(endpoint, signer, options);
await this.assignKeyAddresses(kernelAddress);
}

Expand Down Expand Up @@ -217,6 +212,31 @@ export default class AndromedaClient {
return JSON.parse(Buffer.from(result).toString('utf8')) as T
}

/**
* Wrapper function for CosmWasm query
* https://cosmos.github.io/cosmjs/latest/cosmwasm-stargate/classes/SigningCosmWasmClient.html#queryContractSmart
* @param address
* @param query
* @returns
*/
async queryContractRawAll(address: string, pagination: Partial<PageRequest>) {
this.preMessage();
const result = await this.chainClient!.rawQueryClient!.wasm.getAllContractState(
address,
await this.encodePagination(pagination)
);
return result.models.map(model => ({
key: Buffer.from(model.key).toString('utf8'),
value: Buffer.from(model.value).toString('utf8')
}))
}

async encodePagination(pagination: Partial<PageRequest>) {
return PageRequest.encode(PageRequest.fromPartial(pagination)).finish();
}

/**
* Wrapper function for CosmWasm migrate
/**
* Wrapper function for CosmWasm migrate
* https://cosmos.github.io/cosmjs/latest/cosmwasm-stargate/classes/SigningCosmWasmClient.html#migrate
Expand Down Expand Up @@ -428,13 +448,12 @@ export default class AndromedaClient {
* @param gas
* @returns
*/
calculcateFee(gas: number) {
calculcateFee(gas: number, multiplier = 1.3) {
const gasPrice = this.chainClient?.gasPrice;
if (!gasPrice)
throw new Error(
"No gas prices provided for client. Cannot simulate Tx fee."
);
const multiplier = 1.3; // Unsure why this is necessary but is added during simulateTx in cosmjs
return calculateFee(Math.round(gas * multiplier), gasPrice);
}

Expand Down
4 changes: 3 additions & 1 deletion packages/andrjs/src/clients/BaseChainClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const DEFAULT_CONFIG: ChainClient['config'] = {
* Base class for chain clients, implementing partial functionality of ChainClient
*/
export default class BaseChainClient implements Partial<ChainClient> {
public addressPrefix: string;
protected signingClient?: SigningStargateClient;
public queryClient?: ChainClient["queryClient"];
public signer = "";
Expand All @@ -44,7 +45,8 @@ export default class BaseChainClient implements Partial<ChainClient> {
* Creates a new BaseChainClient instance
* @param config - Optional partial configuration to override defaults
*/
constructor(config?: Partial<ChainClient['config']>) {
constructor(addressPrefix: string, config?: Partial<ChainClient['config']>) {
this.addressPrefix = addressPrefix;
this.config = {
...DEFAULT_CONFIG,
...config,
Expand Down
26 changes: 22 additions & 4 deletions packages/andrjs/src/clients/ChainClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,30 @@ import type {
MsgMigrateContractEncodeObject,
MsgStoreCodeEncodeObject,
UploadResult,
WasmExtension,
} from "@cosmjs/cosmwasm-stargate";
import type { Coin, EncodeObject, OfflineDirectSigner, OfflineSigner } from "@cosmjs/proto-signing";
import type { AminoTypes, GasPrice, MsgSendEncodeObject, QueryClient, SigningStargateClient, SigningStargateClientOptions, TxExtension } from "@cosmjs/stargate";
import type { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import type { Fee, Msg } from "../types";
import { CometClient, RpcClient } from "@cosmjs/tendermint-rpc";
import { SimulateResponse } from "cosmjs-types/cosmos/tx/v1beta1/service";

/**
* When interacting with any Cosmos chain there may be differences in how they sign messages or how the messages themselves are constructed.
* This class is used to provide a generic interface for interacting with any Cosmos chain and is used by our AndromedaClient class.
* Most of the methods are simply wrappers however some require specific implementations.
*/
export default interface ChainClient {

addressPrefix: string;
chainId?: string;

// The client used to query the chain
queryClient?: CosmWasmClient;

// tx query client;
txQueryClient?: QueryClient & TxExtension
rawQueryClient?: QueryClient & TxExtension & WasmExtension

// The current signer address
signer: string;
commectClient?: CometClient;
Expand All @@ -53,10 +59,9 @@ export default interface ChainClient {
* @param rpcClient - Optional RPC client.
*/
connect(
endpoint: string,
endpoint: string | RpcClient,
signer?: OfflineSigner | OfflineDirectSigner,
options?: SigningStargateClientOptions,
rpcClient?: RpcClient
): Promise<void>;
/**
* Disconnects from the current chain completely
Expand Down Expand Up @@ -120,6 +125,19 @@ export default interface ChainClient {
memo?: string,
funds?: readonly Coin[]
): Promise<ExecuteResult>;


/**
* Simulates all given messages and returns a gas fee estimate
* @param messages
* @param fee
* @param memo
*/
simulateRaw(
messages: readonly EncodeObject[],
memo?: string
): Promise<SimulateResponse>;

/**
* Simulates an execute message and returns a gas fee estimate
* @param address
Expand Down
55 changes: 46 additions & 9 deletions packages/andrjs/src/clients/CosmClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
InstantiateOptions,
InstantiateResult,
MigrateResult,
setupWasmExtension,
UploadResult,
wasmTypes,
} from "@cosmjs/cosmwasm-stargate";
Expand Down Expand Up @@ -53,6 +54,7 @@ import { fromBase64 } from "@cosmjs/encoding";
import {
CometClient,
HttpBatchClient,
HttpClient,
RpcClient,
Tendermint37Client,
} from "@cosmjs/tendermint-rpc";
Expand All @@ -66,11 +68,12 @@ import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing";
export default class CosmClient extends BaseChainClient implements ChainClient {
/** Offline signer for transaction signing */
private signerWallet?: OfflineSigner | OfflineDirectSigner;
public chainId?: string;

/** Client for querying blockchain data */
public queryClient?: ChainClient["queryClient"];
/** Client for querying transaction data */
public txQueryClient?: ChainClient["txQueryClient"];
/** Client for querying data with raw implementation */
public rawQueryClient?: ChainClient["rawQueryClient"];
/** Client for interacting with the Comet BFT consensus engine */
public cometClient?: CometClient | undefined;

Expand All @@ -87,21 +90,30 @@ export default class CosmClient extends BaseChainClient implements ChainClient {
* @param rpcClient - Optional RPC client
*/
async connect(
endpoint: string,
endpoint: string | RpcClient,
signer?: OfflineSigner | OfflineDirectSigner,
options?: SigningStargateClientOptions,
rpcClient?: RpcClient
): Promise<void> {
delete this.signingClient;
delete this.queryClient;
this.gasPrice = options?.gasPrice;
this.signerWallet = signer;
const cometClient = await Tendermint37Client.create(rpcClient ?? new HttpBatchClient(endpoint));
// Nibiru rpc somehow doesn't work with HttpBatchClient
if (typeof endpoint === 'string') {
if (this.addressPrefix === 'nibi') {
endpoint = new HttpClient(endpoint);
} else {
endpoint = new HttpBatchClient(endpoint);
}
}

const cometClient = await Tendermint37Client.create(endpoint);
this.cometClient = cometClient;
this.queryClient = await CosmWasmClient.create(cometClient);
this.txQueryClient = QueryClient.withExtensions(
this.rawQueryClient = QueryClient.withExtensions(
cometClient,
setupTxExtension
setupTxExtension,
setupWasmExtension
);
if (signer) {
const aminoTypes = options?.aminoTypes ?? new AminoTypes({
Expand All @@ -125,7 +137,9 @@ export default class CosmClient extends BaseChainClient implements ChainClient {

const [account] = await signer.getAccounts();
this.signer = account.address;

}
this.chainId = await this.queryClient!.getChainId();
}

/**
Expand Down Expand Up @@ -164,6 +178,30 @@ export default class CosmClient extends BaseChainClient implements ChainClient {
return this.signingClient!.simulate(this.signer, messages, memo);
}

/**
* Simulates multiple messages execution
* @param messages - Array of messages to simulate
* @param _fee - Optional fee
* @param memo - Optional memo
*/
public async simulateRaw(
messages: EncodeObject[],
memo?: string
) {
const anyMsgs = messages.map((m) => this.signingClient!.registry.encodeAsAny(m));
const accountFromSigner = (await this.signerWallet!.getAccounts()).find((account) => account.address === this.signer);
if (!accountFromSigner) {
throw new Error("Failed to retrieve account from signer");
}
const pk = { ...encodeSecp256k1Pubkey(accountFromSigner.pubkey) }
if (this.config.accountPubKeyTypeUrl) {
pk.type = this.config.accountPubKeyTypeUrl as any;
}
const { sequence } = await this.signingClient!.getSequence(this.signer);
const result = await this.rawQueryClient!.tx.simulate(anyMsgs, memo, pk, sequence);
return result
}

/**
* Signs and broadcasts multiple messages
* @param messages - Array of messages to sign and broadcast
Expand Down Expand Up @@ -229,11 +267,10 @@ export default class CosmClient extends BaseChainClient implements ChainClient {
const { accountNumber, sequence } = await this.signingClient!.getSequence(
this.signer
);
const chainId = await this.signingClient!.getChainId();
const signerData: SignerData = {
accountNumber: accountNumber,
sequence: sequence,
chainId: chainId,
chainId: this.chainId!,
};

return isOfflineDirectSigner(this.signerWallet!)
Expand Down
6 changes: 3 additions & 3 deletions packages/andrjs/src/clients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ export { default as ChainClient } from "./ChainClient";
export default function createClient(addressPrefix: string, options?: Partial<ChainClient['config']>): ChainClient {
switch (addressPrefix) {
case "inj":
return new CosmClient({ ...options, storeCodeEvent: 'cosmwasm.wasm.v1.EventCodeStored', 'accountPubKeyTypeUrl': '/injective.crypto.v1beta1.ethsecp256k1.PubKey' });
return new CosmClient(addressPrefix, { ...options, storeCodeEvent: 'cosmwasm.wasm.v1.EventCodeStored', 'accountPubKeyTypeUrl': '/injective.crypto.v1beta1.ethsecp256k1.PubKey' });
case "titan":
case "uptick":
return new CosmClient({ ...options, 'accountPubKeyTypeUrl': '/ethermint.crypto.v1.ethsecp256k1.PubKey' });
return new CosmClient(addressPrefix, { ...options, 'accountPubKeyTypeUrl': '/ethermint.crypto.v1.ethsecp256k1.PubKey' });
default:
return new CosmClient({ ...options });
return new CosmClient(addressPrefix, { ...options });
}
}
16 changes: 10 additions & 6 deletions packages/andrjs/src/wallets/Wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,15 @@ export default class Wallet {
async decrypt(
password: string,
): Promise<string> {
const kdfConfiguration = basicPasswordHashingOptions;
const encryptionKey = await executeKdf(password, kdfConfiguration);
const keyBuffer = Buffer.from(this.key, 'hex').valueOf();
const nonce = keyBuffer.slice(0, xchacha20NonceLength);
const decrypted = await Xchacha20poly1305Ietf.decrypt(keyBuffer.slice(xchacha20NonceLength), encryptionKey, nonce);
return Buffer.from(decrypted).toString('ascii')
try {
const kdfConfiguration = basicPasswordHashingOptions;
const encryptionKey = await executeKdf(password, kdfConfiguration);
const keyBuffer = Buffer.from(this.key, 'hex').valueOf();
const nonce = keyBuffer.slice(0, xchacha20NonceLength);
const decrypted = await Xchacha20poly1305Ietf.decrypt(keyBuffer.slice(xchacha20NonceLength), encryptionKey, nonce);
return Buffer.from(decrypted).toString('ascii')
} catch (e) {
throw new Error("Invalid password");
}
}
}
2 changes: 1 addition & 1 deletion packages/cli/src/cliBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export async function start() {
const inputs = process.argv.slice(2);
if (inputs.length === 0) {
await title();
await State.connectClient()
await State.connectClient().catch(_ => { })
while (true) {
let input = await ask();
const { _: cmd, ...flags } = minimist(parseInput(input.command));
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/handlers/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,8 +492,8 @@ export async function setCurrentWallet(
wallet: Wallet,
passphrase?: string,
) {
passphrase = passphrase ?? (await State.wallets.getWalletPassphrase(wallet.name));
const signer = await wallet.getWallet(passphrase);

const signer = await State.wallets.getWalletSigner(wallet, passphrase);
State.wallets.defaultWallet = wallet.name;

try {
Expand Down
Loading

0 comments on commit f77ddd1

Please sign in to comment.