- Running wasmd
- Gas and limits
- IBC
- Multitest
- Submessages
- Endpoints
- Raw data
- Database dumps
- Determinstic types
- Hacking wasmd
- Custom modules in clients
after make install
(or on apple silicon: LEDGER_ENABLED=false make install
)
our chain-id is going to be localwasmd
our gas denomination is going to be configured to uwasm
staking denomination is the default ustake
the following sortof mimics the explicit steps in https://github.com/CosmWasm/wasmd/blob/main/contrib/local/setup_wasmd.sh
adding two users: tester1 and validator1
- wipe
~/.wasmd
if it exists wasmd init localwasmd --chain-id localwasmd --overwrite
- edit
~/.wasmd/config/app.toml
- set
minimum-gas-prices
to0.025uwasm
- set
- edit
~/.wasmd/config/client.toml
- set
chain-id
tolocalwasmd
- set
- edit
~/.wasmd/config/genesis.json
- change
stake
toustake
- change
- edit
~/.wasmd/config/config.toml
- under Consensus Configuration: change all the
timeout_
stuff to200ms
for faster local dev block time
- under Consensus Configuration: change all the
wasmd keys add tester1
- wasmd add-genesis-account $(wasmd keys show -a tester1) 10000000000uwasm,10000000000ustake
wasmd keys add validator1
- wasmd add-genesis-account $(wasmd keys show -a validator1) 10000000000uwasm,10000000000ustake
- wasmd gentx validator1 "250000000ustake" --chain-id="localwasmd" --amount="250000000ustake"
- wasmd collect-gentxs
now wasmd start
should just work
for hacking around the sourcecode, see Hacking wasmd
Just follow the Readme
But for convenience, in Taskfile syntax where you would then just run task dev
:
version: '3'
vars:
ADDRS:
address-1
address-2
PASSWORD: some-password1234567!
tasks:
start:
env:
DOCKER_DEFAULT_PLATFORM: linux/amd64
cmds:
- docker volume rm -f wasmd_data
- docker run --rm -it
-e PASSWORD={{.PASSWORD}}
--mount type=volume,source=wasmd_data,target=/root
cosmwasm/wasmd:latest
/opt/setup_wasmd.sh {{.ADDRS}}
- docker run --rm -it -p 26657:26657 -p 26656:26656 -p 1317:1317
--mount type=volume,source=wasmd_data,target=/root
cosmwasm/wasmd:latest
/opt/run_wasmd.sh
-
Cosm costs for storage read/write here: https://github.com/cosmos/cosmos-sdk/blob/0360c3d87f6fb19c6d0bb509fb9b340b48bbe670/store/types/gas.go#L232
-
Wasmd costs for contract loading, events, messages, execution etc. are here: https://github.com/CosmWasm/wasmd/blob/9050b5fa4e21f7abb03e5eb46130d735fb47f353/x/wasm/types/gas_register.go#L13
-
Cosm limits here: https://github.com/CosmWasm/cosmwasm/blob/5220ed7aee3245d624e1756e7acdb03dfc820f74/packages/vm/src/imports.rs#L30
-
CosmWasm limits for native API (e.g. crypto): https://github.com/CosmWasm/cosmwasm/blob/8a652d7cd8071f71139deca6be8194ed4a278b2c/packages/vm/src/environment.rs#L47-L63
Keys can be "complex" and are concatenated with an internal namespacing scheme, which both contribute to their hard limits.
Max gas for executions is per-block and configured in the genesis file, e.g.
"consensus_params": {
"block": {
"max_gas": "SET_YOUR_MAX_VALUE",
Max gas for queries is configured per-node the app.toml
There are also size limits, such as the maximum transaction size, defined in the config.toml
max_tx_bytes
var. This applies to all transactions, including code upload.
VM computational costs are tuned to be very cheap. Not free, but, for typical financial products, it's unlikely to be the culprit for runaway gas costs
Smart queries are generally cheap themselves, but incur the same contract loading cost as the initial query (60k or so). Therefore they are, in practice, quite expensive. This can be avoided by voting to "pin" hot contracts, but generally speaking, use raw queries where possible.
Generally speaking, costs from most expensive to most cheap:
- Contract Load (happens when you smart-query a contract that isn't pinned) - roughly 60,000 SDK units
- Storage (see above - both the frequency of read/write actions and the size of data matter). Can easily add up to thousands of SDK units for simple usage
- Computation - relatively cheap, tuned to be about 1000 VM instructions per 1 sdk unit.
In practical terms, if you're optimizing for gas, consider the following:
- More monolothic contracts to avoid cross-contract queries
- Raw queries instead of smart queries
- Think very carefully about storage access patterns
- Cache storage lookups that stay consistent through a call in memory
- Don't worry about simple typical code like iterating over a Vec or cloning a String. It' relatively negligible. Heavy computation like calculating a hash can take around 100 gas units (still not worrisome, in most cases)
The following uses juno/osmosis testnets and the Go relayer as examples, but the concepts are tool-agnostic
rly chains add --file ./juno-testnet.json juno-testnet
rly chains add --file ./osmosis-testnet.json osmosis-testnet
Here's an example config file for Juno testnet:
{
"type": "cosmos",
"value": {
"key": "default",
"chain-id": "uni-6",
"rpc-addr": "https://rpc.uni.junonetwork.io:443",
"account-prefix": "juno",
"keyring-backend": "test",
"gas-adjustment": 1.5,
"gas-prices": "0.0026ujunox",
"debug": true,
"timeout": "20s",
"output-format": "json",
"sign-mode": "direct"
}
}
rly keys restore juno-testnet default "[MNEMONIC]"
rly keys restore osmosis-testnet default "[MNEMONIC]"
Sanity check that network config is right and wallets have gas tokens
rly q balance juno-testnet
rly q balance osmosis-testnet
This is a go-relayer config thing, not general IBC. We decide the path name, which is “foo” here. Note that here it uses the ChainId, not the network names from above. foo
(or whatever path name you choose) will be used in more commands later, instead of referencing each chain every time.
rly paths new uni-6 osmo-test-5 foo
I believe it needs to be made from each side (using the path name from above)
rly transact client juno-testnet osmosis-testnet foo --debug --override
rly transact client osmosis-testnet juno-testnet foo --debug --override
rly transact connection foo --debug --override
This is assigned automatically when you upload a contract with the required IBC handlers, and the port can be queried by looking up the contract info, e.g. with a cosmjs client.getContract()
call.
Since the contract/port has changed, you'll need to create the channels from scratch too.
This is just some string that the protocol should be aware of for sanity checking and compatibility. Here it will be "bar-001". This version can stay consistent as you create channels, it's just a way to make sure both sides of the channel understand eachother.
A contract may have multiple channels, e.g. for connecting a contract on one chain to contracts on multiple other chains (e.g. "mint nfts" on stargaze, "send tokens" on osmosis, etc.)
rly transact channel foo --src-port [wasm.somejunoaddress] --dst-port [wasm.someosmosisaddress] --order unordered --version bar-001 --debug --override
That's it!
Now we can start the relayer:
rly start foo --debug
There are more steps involved here, but TL;DR:
// called on both sides, `OpenInit` and `OpenTry`, but logic is usually the same
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc_channel_open(deps: DepsMut, env: Env, msg: IbcChannelOpenMsg) -> Result<IbcChannelOpenResponse> {
// validate channel version, ordering, etc.
// almost always want to return `None` here
Ok(None)
}
// called on both sides when channel is connected
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc_channel_connect(deps: DepsMut, env: Env, msg: IbcChannelConnectMsg) -> Result<IbcBasicResponse> {
// also validate channel like in ibc_channel_open
// it's possible for a contract to connect to multiple channels - and this can be differentiated by checking `msg.channel().version`
// store channel in state
Ok(IbcBasicResponse::default())
}
// called on destination side when a packet is received
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc_packet_receive(deps: DepsMut, env: Env, msg: IbcPacketReceiveMsg) -> Result<IbcReceiveResponse> {
// parse `msg.packet.data`
// see next section about acknowledgements and error handling
}
// called on sender side when the destination side acknowledges the packet
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc_packet_ack(deps: DepsMut, env: Env, ack: IbcPacketAckMsg) -> Result<IbcBasicResponse> {
// if anything needs to be finalized on the sender side:
// check ack.acknolwedgement.data
// parse ack.original_packet.data
}
When the receiving side is finished processing an IBC packet, it may set the acknowledgement in the IbcReceiveResponse
type. This data does not have any inherent meaning, but it is likely to require keeping it consistent so that the sender can check the acknowledgement for success, error, etc.
If a contract errors out (e.g. returns Err instead of Ok), then the CosmWasm module on the go side will automatically revert the contract state and turn the acknowledgement into a predefined error acknowledgement type corresponding to StdAck. In this case, the error can be recovered via:
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc_packet_ack(deps: DepsMut, env: Env, ack: IbcPacketAckMsg) -> Result<IbcBasicResponse> {
let data:StdAck = from_json(ack.acknowledgement.data)?;
if let StdAck::Error(err) = data {
// do something with error
}
//...
}
// write an IBC packet to my channel
// this channel info was stored in `ibc_channel_connect()`
let channel_id = self
.get_ibc_channel(storage)?
.endpoint
.channel_id;
response.add_message(IbcMsg::SendPacket {
channel_id,
data: to_binary(&msg)?,
timeout: IbcTimeout::with_timestamp(self.env.block.time.plus_seconds(TIMEOUT_SECONDS)),
});
- IBC explanation / spec: https://github.com/CosmWasm/cosmwasm/blob/main/IBC.md
- IBC standards: https://github.com/cosmos/ibc#interchain-standards
- Simple example w/ comments: https://github.com/0xekez/cw-ibc-example
- Another example: https://github.com/public-awesome/ics721
The overall idea is that it must be something that satisfies the Contract
trait in multitest.
Out of the box, this can be done with ContractWrapper
, as per the docs: https://docs.rs/cw-multi-test/latest/cw_multi_test/#contracts
Once you have a valid contract, it's very simple:
let mut app = App::default();
let code_id = app.store_code(get_contract());
let contract_addr = app.instantiate_contract(
code_id,
Addr::unchecked("sender-addr"),
&InstantiateMsg { },
&[],
"mycontract",
Some("admin-addr"),
)?;
// query
app.wrap().query_wasm_smart(contract_addr, &QueryMsg::Foo {});
// execute
let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: contract_addr.to_string(),
msg: to_binary(ExecuteMsg::Foo{})?,
funds: vec![]
});
app.execute(sender.clone(), cosmos_msg)?;
// update block time
app.update_block(|block_info| {
block_info.time = block_info.time.plus_nanos(/* some nanos, to emulate chain speed */);
block_info.height += 1;
})
app.sudo(SudoMsg::Bank(BankSudo::Mint {
to_address: recipient.to_string(),
amount: vec![Coin::new(100, "uwasm")]
}));
let balance = app.wrap().query_balance(recipient, "uwasm")?;
The full semantics for submessages are described in the doc
One important takeaway is that if you have a reply handler for submessage error (ReplyOn::Always or ReplyOn::Error), then only the sub-contract state is reverted.
For example, a contract may send a submessage to execute itself, and gain the following ability:
- if the submessage errors, all the state from that call in the subcontract is reverted
- if the reply handler returns Ok, then it can still change state in this parent level (if it returns Err then its state is reverted too, of course)
This is a useful pattern for creating, effectively, sub-transactions and not having to manually roll-back state while still mutating some state like a log of failures.
See the registry at https://github.com/cosmos/chain-registry/tree/master (testnets are a subdir: https://github.com/cosmos/chain-registry/tree/master/testnets)
Each chain has a JSON config that specifies some RPC/GRPC endpoints.
Websockets might exist on the rpc node with wss://
and /websocket
at the end, but YMMV
Chains generally also provide an LCD endpoint for doing things like querying a native token price at a specific block height adding ?height=BLOCKNUMBER.
Use base64 and protobuf to decode raw data. Example:
echo "CrgBCrABCiYvdGVycmEud2FzbS52MWJldGExLk1zZ0V4ZWN1dGVDb250cmFjdBKFAQosdGVycmExcDAyNDJnZXdweDhwY3I4aG5wNTd5bG16ZWY2bWtwNjY0eXN4dzQSLHRlcnJhMWc3ampqa3Q1dXZramV5aHA4ZWNkejRlNGh2dG44M3N1ZDN0bWgyGid7ImV4ZWN1dGVfc3RyYXRlZ3kiOnsic3RyYXRlZ3lfaWQiOjI2fX0Ym6rpAhJqClEKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDAgrw7ozay5jSmwGydzr3hZ57XKLZhheZ+C2AIAIjBLoSBAoCCH8YuHUSFQoOCgR1dXNkEgY1NjM1NzMQ3KjlARpAqjHfsg1dT8Lg9SeqgOC50m55Hmu1dY4heKdbV09oGp0Jg1LLLcsNbhcgFjicZ23frMdLpysjIF4Xe65qiQZ35Q==" | base64 -D | protoc --decode_raw
Here's a bit of helper code to load cw-storage-plus things via raw queries. This is adapted from https://github.com/CosmWasm/cw-storage-plus/blob/69300779519d8ba956fb53725e44e2b59c317b1c/src/helpers.rs#L57 originally, and the abstraction is copy/pasted with a minor tweak from the Levana sourcecode
/// Load an [cw_storage_plus::Item] stored in an external contract
pub fn load_external_item<T: serde::de::DeserializeOwned>(
querier: &QuerierWrapper<Empty>,
contract_addr: impl Into<String>,
key: impl Into<Binary>,
) -> anyhow::Result<T> {
// only deserialize for extra context if in debug mode
// because we must pass the key in as an owned value
// and so we have to extract the name in the happy path too
let key: Binary = key.into();
let debug_key_name = if cfg!(debug_assertions) {
from_binary::<String>(&key).ok()
} else {
None
};
external_helper(querier, contract_addr, key, || {
anyhow!("unable to load external item {}", debug_key_name.unwrap_or_default())
})
}
/// Load a value from a [cw_storage_plus::Map] stored in an external contract
pub fn load_external_map<'a, T: serde::de::DeserializeOwned>(
querier: &QuerierWrapper<Empty>,
contract_addr: impl Into<String>,
namespace: &str,
key: &impl PrimaryKey<'a>,
) -> anyhow::Result<T> {
external_helper(querier, contract_addr, map_key(namespace, key), || {
anyhow!("unable to load external map {}", namespace)
})
}
/// Check if a [cw_storage_plus::Map] in an external contract has a specific key
pub fn external_map_has<'a>(
querier: &QuerierWrapper<Empty>,
contract_addr: impl Into<String>,
namespace: &str,
key: &impl PrimaryKey<'a>,
) -> anyhow::Result<bool> {
querier
.query_wasm_raw(contract_addr, map_key(namespace, key))
.map(|x| x.is_some())
.map_err(|e| e.into())
}
fn external_helper<T: serde::de::DeserializeOwned>(
querier: &QuerierWrapper<Empty>,
contract_addr: impl Into<String>,
key: impl Into<Binary>,
mk_error_message: impl FnOnce() -> anyhow::Error,
) -> anyhow::Result<T> {
let res = querier
.query_wasm_raw(contract_addr, key)?
.ok_or_else(mk_error_message)?;
serde_json_wasm::from_slice(&res).map_err(|err| err.into())
}
pub fn map_key<'a, K: PrimaryKey<'a>>(namespace: &str, key: &K) -> Vec<u8> {
let mut size = namespace.len();
let key = key.key();
assert!(!key.is_empty());
for x in &key {
size += x.as_ref().len() + 2;
}
let mut out = Vec::<u8>::with_capacity(size);
for prefix in std::iter::once(namespace.as_bytes())
.chain(key.iter().take(key.len() - 1).map(|key| key.as_ref()))
{
out.extend_from_slice(&encode_length(prefix));
out.extend_from_slice(prefix);
}
if let Some(last) = key.last() {
out.extend_from_slice(last.as_ref());
}
out
}
fn encode_length(bytes: &[u8]) -> [u8; 2] {
if let Ok(len) = u16::try_from(bytes.len()) {
len.to_be_bytes()
} else {
panic!("only supports namespaces up to length 0xFFFF")
}
}
Available via some third-party providers, e.g. https://quicksync.io/networks/osmosis.html
From app.toml:
# default: the last 100 states are kept in addition to every 500th state; pruning at 10 block intervals
# nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node)
# everything: all saved states will be deleted, storing only the current state; pruning at 10 block intervals
# custom: allow pruning options to be manually specified through 'pruning-keep-recent', 'pruning-keep-every', and 'pruning-interval'
pruning = "default"
Contract types must be deterministic, i.e. re-runing the same code with the same state and inputs must always produce the same results. Therefore, by default, these types cannot be used as they depend on randomization:
- HashMap
- HashSet
Additionally, usize
is not allowed since it would be different when running off-chain tests (which typically run on 64 bit architectures), and there have historically been some problems with serialization.
Instead, use BTreeMap
, which is deterministic, and u32
explicitly.
wasmd start --log_level panic --trace
(panic may be a bit extreme - point is some level lower than info, which is just way too noisy to see anything)deps.api.debug()
contract messages will now appear in the wasmd console
Some tips for how it all fits together: CosmWasm/cosmwasm#746 (comment)
SETUP
- in wasmd go.mod
replace( github.com/CosmWasm/wasmvm => path/to/my/wasmvm)
- in local
wasmvm
edit libwasmvm's Cargo.toml to point at a localcosmwasm-vm
viapath = ...
- generally point at local crates as-needed (e.g. a local
cosmwasm-std
, localcw-storage-plus
, etc.)
ITERATIVE DEV
- Make changes in local cosmwasm-vm
- recompile the
libwasmvm
(make build-rust
inwasmvm
) - rebuild wasmd so it will use this new wasmvm build (
LEDGER_ENABLED=false make install
on osx)
e.g. editing do_debug()
like this will show some interesting info:
println!("GAS LEFT: {}", env.get_gas_left());
env.with_gas_state(|gas_state| {
println!("GAS STATE: {:?}", gas_state);
});
Out of the box, cosmjs comes with support for popular modules like Bank, CosmWasm, etc.
However, a chain usually builds on wasmd in order to add new modules, which in turn requires that clients be aware of this. Usually, the chains then provide a custom client implementation, however, this isn't strictly necessary since CosmJS provides a way to create methods using custom protobuf definitions.
The official documentation is here: https://github.com/cosmos/cosmjs/blob/main/packages/stargate/CUSTOM_PROTOBUF_CODECS.md
First we need to build the definitions, then we can use them in client code.
The steps are:
- download all the protobuf files necessary
- this requires some digging as there are often interdependencies
- compile all the protobuf definitions with the typescript generation plugin
here's an example in Taskfile where you'd run task generate
to generate Osmosis tokenfactory protobuf definitions from start to finish.
This assumes you have protoc installed and a scripts
directory with just the ts-proto
node_modules dependency (i.e. a minimal npm/yarn package.json)
version: '3'
vars:
PROTO_TEMP_DIR: proto-temp
PROTO_OUTPUT_DIR: proto-ts
COSMOS_SDK_VERSION: 'v0.47.1'
COSMOS_PROTO_VERSION: 'v1.0.0-beta.3'
OSMOSIS_VERSION: 'master'
REGEN_VERSION: 'v1.3.3-alpha.regen.1'
GOOGLE_VERSION: 'master'
COSMOS_SDK_BASE_PATH: "cosmos/base/v1beta1"
COSMOS_SDK_QUERY_PATH: "cosmos/base/query/v1beta1"
COSMOS_SDK_BANK_PATH: "cosmos/bank/v1beta1"
COSMOS_SDK_AMINO_PATH: "amino"
COSMOS_SDK_MSG_PATH: "cosmos/msg/v1"
tasks:
generate:
cmds:
- task: download
- task: compile
- echo "Finished"
download:
deps: [
"download-proto-cosmos",
"download-proto-cosmos-sdk",
"download-proto-gogo",
"download-proto-google",
"download-proto-tokenfactory"
]
compile:
cmds:
- mkdir -p "{{.PROTO_OUTPUT_DIR}}"
- cd scripts && protoc --plugin="./node_modules/.bin/protoc-gen-ts_proto" --ts_proto_out="../{{.PROTO_OUTPUT_DIR}}" --proto_path="../{{.PROTO_TEMP_DIR}}" --ts_proto_opt="esModuleInterop=true,forceLong=long,useOptionals=all" "../{{.PROTO_TEMP_DIR}}/osmosis/tokenfactory/v1beta1/tx.proto" "../{{.PROTO_TEMP_DIR}}/osmosis/tokenfactory/v1beta1/query.proto"
download-proto-cosmos:
cmds:
- mkdir -p "{{.PROTO_TEMP_DIR}}/cosmos_proto"
- curl https://raw.githubusercontent.com/cosmos/cosmos-proto/{{.COSMOS_PROTO_VERSION}}/proto/cosmos_proto/cosmos.proto -o {{.PROTO_TEMP_DIR}}/cosmos_proto/cosmos.proto
download-proto-cosmos-sdk:
cmds:
- mkdir -p "{{.PROTO_TEMP_DIR}}/{{.COSMOS_SDK_BASE_PATH}}"
- mkdir -p "{{.PROTO_TEMP_DIR}}/{{.COSMOS_SDK_QUERY_PATH}}"
- mkdir -p "{{.PROTO_TEMP_DIR}}/{{.COSMOS_SDK_BANK_PATH}}"
- mkdir -p "{{.PROTO_TEMP_DIR}}/{{.COSMOS_SDK_AMINO_PATH}}"
- mkdir -p "{{.PROTO_TEMP_DIR}}/{{.COSMOS_SDK_MSG_PATH}}"
- curl https://raw.githubusercontent.com/cosmos/cosmos-sdk/{{.COSMOS_SDK_VERSION}}/proto/{{.COSMOS_SDK_BASE_PATH}}/coin.proto -o {{.PROTO_TEMP_DIR}}/{{.COSMOS_SDK_BASE_PATH}}/coin.proto
- curl https://raw.githubusercontent.com/cosmos/cosmos-sdk/{{.COSMOS_SDK_VERSION}}/proto/{{.COSMOS_SDK_QUERY_PATH}}/pagination.proto -o {{.PROTO_TEMP_DIR}}/{{.COSMOS_SDK_QUERY_PATH}}/pagination.proto
- curl https://raw.githubusercontent.com/cosmos/cosmos-sdk/{{.COSMOS_SDK_VERSION}}/proto/{{.COSMOS_SDK_BANK_PATH}}/bank.proto -o {{.PROTO_TEMP_DIR}}/{{.COSMOS_SDK_BANK_PATH}}/bank.proto
- curl https://raw.githubusercontent.com/cosmos/cosmos-sdk/{{.COSMOS_SDK_VERSION}}/proto/{{.COSMOS_SDK_AMINO_PATH}}/amino.proto -o {{.PROTO_TEMP_DIR}}/{{.COSMOS_SDK_AMINO_PATH}}/amino.proto
- curl https://raw.githubusercontent.com/cosmos/cosmos-sdk/{{.COSMOS_SDK_VERSION}}/proto/{{.COSMOS_SDK_MSG_PATH}}/msg.proto -o {{.PROTO_TEMP_DIR}}/{{.COSMOS_SDK_MSG_PATH}}/msg.proto
# actually download from regen, see https://github.com/cosmos/cosmos-sdk/issues/12984#issuecomment-1275674526
download-proto-gogo:
cmds:
- mkdir -p {{.PROTO_TEMP_DIR}}/gogoproto
- curl https://raw.githubusercontent.com/regen-network/protobuf/{{.REGEN_VERSION}}/gogoproto/gogo.proto -o {{.PROTO_TEMP_DIR}}/gogoproto/gogo.proto
download-proto-google:
cmds:
- mkdir -p {{.PROTO_TEMP_DIR}}/google/api
- curl https://raw.githubusercontent.com/googleapis/googleapis/{{.GOOGLE_VERSION}}/google/api/annotations.proto -o {{.PROTO_TEMP_DIR}}/google/api/annotations.proto
- curl https://raw.githubusercontent.com/googleapis/googleapis/{{.GOOGLE_VERSION}}/google/api/http.proto -o {{.PROTO_TEMP_DIR}}/google/api/http.proto
download-proto-tokenfactory:
cmds:
- mkdir -p {{.PROTO_TEMP_DIR}}/osmosis/tokenfactory/v1beta1
- curl https://raw.githubusercontent.com/osmosis-labs/osmosis/{{.OSMOSIS_VERSION}}/proto/osmosis/tokenfactory/v1beta1/authorityMetadata.proto -o {{.PROTO_TEMP_DIR}}/osmosis/tokenfactory/v1beta1/authorityMetadata.proto
- curl https://raw.githubusercontent.com/osmosis-labs/osmosis/{{.OSMOSIS_VERSION}}/proto/osmosis/tokenfactory/v1beta1/genesis.proto -o {{.PROTO_TEMP_DIR}}/osmosis/tokenfactory/v1beta1/genesis.proto
- curl https://raw.githubusercontent.com/osmosis-labs/osmosis/{{.OSMOSIS_VERSION}}/proto/osmosis/tokenfactory/v1beta1/params.proto -o {{.PROTO_TEMP_DIR}}/osmosis/tokenfactory/v1beta1/params.proto
- curl https://raw.githubusercontent.com/osmosis-labs/osmosis/{{.OSMOSIS_VERSION}}/proto/osmosis/tokenfactory/v1beta1/query.proto -o {{.PROTO_TEMP_DIR}}/osmosis/tokenfactory/v1beta1/query.proto
- curl https://raw.githubusercontent.com/osmosis-labs/osmosis/{{.OSMOSIS_VERSION}}/proto/osmosis/tokenfactory/v1beta1/tx.proto -o {{.PROTO_TEMP_DIR}}/osmosis/tokenfactory/v1beta1/tx.proto
Assuming we have a project setup with all the dependencies and the output from the above step in src/proto-ts
for a project, we can then do something like this to add functionality to our client instance:
import { Registry } from "@cosmjs/proto-signing";
import { Event, QueryClient, createProtobufRpcClient } from "@cosmjs/stargate";
import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
import { QueryClientImpl } from "./proto-ts/osmosis/tokenfactory/v1beta1/query";
import {protobufPackage, MsgCreateDenom, MsgChangeAdmin, MsgSetBeforeSendHook, MsgMint,} from "./proto-ts/osmosis/tokenfactory/v1beta1/tx";
import {Coin} from "./proto-ts/cosmos/base/v1beta1/coin";
const TypeUrl = {
CreateDenom: `/${protobufPackage}.MsgCreateDenom`,
ChangeAdmin: `/${protobufPackage}.MsgChangeAdmin`,
SetBeforeSendHook: `/${protobufPackage}.MsgSetBeforeSendHook`,
Mint: `/${protobufPackage}.MsgMint`,
};
(async () => {
// Create the client
const signer = await DirectSecp256k1HdWallet.fromMnemonic(
"my super secret seed phrase",
{
prefix: "whatever-addr-prefix",
}
);
const accounts = await signer.getAccounts()
const walletAddress = accounts[0].address
const client = await SigningCosmWasmClient.connectWithSigner(
"http://rpc.example.com",
signer,
{
gasPrice: GasPrice.fromString("0.0125uwasm")
}
);
// Register the new protobuf extensions
client.registry.register(TypeUrl.CreateDenom, MsgCreateDenom);
client.registry.register(TypeUrl.ChangeAdmin, MsgChangeAdmin);
client.registry.register(TypeUrl.SetBeforeSendHook, MsgSetBeforeSendHook);
client.registry.register(TypeUrl.Mint, MsgMint);
// Use it!
const resp = await client.signAndBroadcast(
wallet.address,
[
{
typeUrl: TypeUrl.CreateDenom,
value: MsgCreateDenom.fromPartial({
sender: walletAddress,
subdenom: "mynewdenom"
})
}
],
"auto",
);
console.log(`Transaction success: ${resp.transactionHash}`);
})()