class StdTx(JsonSerializable, JsonDeserializable): __schema__ = S.OBJECT( type=S.STRING_WITH_PATTERN(r"^core/StdTx\Z"), value=S.OBJECT( fee=StdFee.__schema__, msg=S.ARRAY(S.ANY(*(mt.__schema__ for mt in MSG_TYPES.values()))), signatures=S.ARRAY(StdSignature.__schema__), memo=S.STRING, ), ) # NOTE: msg is not plural, and is NOT a typo. This may change later for consistency. fee: Optional[StdFee] = None msg: List[StdMsg] = field(default_factory=list) signatures: List[StdSignature] = field(default_factory=list) memo: str = "" def to_data(self) -> dict: return { "type": "core/StdTx", "value": dict(self.__dict__), } @classmethod def from_data(cls, data: dict) -> StdTx: data = data["value"] fee = StdFee.from_data(data["fee"]) # deserialize the messages msg = [] for m in data["msg"]: msg_type = MSG_TYPES[m["type"]] msg.append(msg_type.from_data(m)) signatures = [StdSignature.from_data(s) for s in data["signatures"]] return cls(fee=fee, msg=msg, signatures=signatures, memo=data["memo"])
class MsgMultiSend(StdMsg): type = "bank/MsgMultiSend" action = "multisend" Input = Input # alias Output = Output # alias __schema__ = S.OBJECT( type=S.STRING_WITH_PATTERN(r"^bank/MsgMultiSend\Z"), value=S.OBJECT(inputs=S.ARRAY(Input.__schema__), outputs=S.ARRAY(Output.__schema__)), ) inputs: List[Union[Input, dict]] outputs: List[Union[Output, dict]] def __init__( self, inputs: List[Input], outputs: List[Output], ): self.inputs = [] self.outputs = [] for i in inputs: if isinstance(i, dict): self.inputs.append( Input(address=i["address"], coins=i["coins"])) elif isinstance(i, Input): self.inputs.append(i) else: raise TypeError( f"invalid item {type(i)} encountered in MsgMultiSend.inputs, can only accept 'Input' or 'dict'." ) for o in outputs: if isinstance(o, dict): self.outputs.append( Output(address=o["address"], coins=o["coins"])) elif isinstance(o, Output): self.outputs.append(o) else: raise TypeError( f"invalid item {type(o)} encountered in MsgMultiSend.outputs, can only accept 'Output' or 'dict'." ) @classmethod def from_data(cls, data: dict) -> MsgMultiSend: data = data["value"] return cls( inputs=[Input.from_data(i) for i in data["inputs"]], outputs=[Output.from_data(o) for o in data["outputs"]], )
class Block(BlockHeader, JsonDeserializable): __schema__ = S.OBJECT( block_meta=BlockHeader.__schema__, block=S.OBJECT( data=S.OBJECT(txs=S.OPTIONAL(S.ARRAY(S.STRING))), evidence={}, last_commit={}, ), ) evidence: JiguBox # TODO: improve and document last_commit: JiguBox # TODO: improve and document txs: List[ Union[str, StdTx, TxInfo, TxInfosQuery]] # ordered last for convenience of pretty printing @classmethod def from_data(cls, data: dict) -> Block: header = BlockHeader.from_data(data["block_meta"]) txs = (data["block"]["data"]["txs"] or []) # these will be Amino-encoded tx strings evidence = JiguBox(data["block"].get("evidence")) last_commit = JiguBox(data["block"].get("last_commit")) return cls(txs=txs, evidence=evidence, last_commit=last_commit, **header.__dict__)
class TxBroadcastResult(JsonSerializable, JsonDeserializable): __schema__ = S.OBJECT( height=S.STRING_INTEGER, txhash=S.STRING, raw_log=S.STRING, logs=S.ARRAY(MsgInfo.__schema__), gas_wanted=S.STRING_INTEGER, gas_used=S.STRING_INTEGER, events=S.ARRAY({}), ) height: int txhash: str raw_log: str # logs: list gas_wanted: int gas_used: int # events: list msgs: MsgInfosQuery @property def pretty_data(self): d = dict(self.__dict__) d.pop("raw_log") return d.items() @property def events(self): return self.msgs.events @classmethod def from_data(cls, data: dict, tx) -> TxBroadcastResult: logs = TxInfo.merge_logs(data, tx) if logs: logs = MsgInfosQuery(logs) return cls( height=data.get("height") and int(data["height"]), txhash=data.get("txhash"), raw_log=data.get("raw_log"), gas_wanted=data.get("gas_wanted") and int(data["gas_wanted"]), gas_used=data.get("gas_used") and int(data["gas_used"]), # events=data.get("events"), msgs=logs, )
class UnbondingDelegation(JsonSerializable, JsonDeserializable): __schema__ = S.OBJECT( delegator_address=S.ACC_ADDRESS, validator_address=S.VAL_ADDRESS, entries=S.ARRAY(UnbondingEntry.__schema__), ) delegator_address: AccAddress validator_address: ValAddress entries: List[UnbondingEntry] @classmethod def from_data(cls, data: Dict[str, Any]) -> UnbondingDelegation: entries = [ UnbondingEntry.from_data(entry) for entry in data["entries"] ] return cls( delegator_address=data["delegator_address"], validator_address=data["validator_address"], entries=entries, )
class Event(JsonSerializable, JsonDeserializable): __schema__ = S.OBJECT( type=S.STRING, attributes=S.ARRAY(S.OBJECT(key=S.STRING, value=S.STRING)) ) type: str attributes: JiguBox[str, List[str]] def __repr__(self) -> str: return f"<Event {self.type}>" def __getitem__(self, item) -> List[str]: return self.attributes[item] def __getattr__(self, name) -> List[str]: if name in self.attributes: return self.attributes[name] return self.__getattribute__(name) @property def pretty_data(self): d = {"type": self.type, **self.attributes.to_data()} return d.items() def to_data(self): return { "type": self.type, "attributes": [ {"key": k, "value": v} for k, vs in self.attributes.items() for v in vs ], } @classmethod def from_data(cls, data: dict) -> Event: attrs = defaultdict(list) for attr in data["attributes"]: attrs[attr["key"]].append(attr["value"]) return cls(type=data["type"], attributes=JiguBox(attrs))
class PublicKey(JsonSerializable, JsonDeserializable): __schema__ = S.OBJECT( type=S.STRING, value=S.ANY( S.STRING, S.OBJECT( threshold=S.STRING_INTEGER, pubkeys=S.ARRAY(S.OBJECT(type=S.STRING, value=S.STRING)), ), ), ) type: str = "tendermint/PubKeySecp256k1" value: Any = None @classmethod def from_data(cls, data: dict) -> PublicKey: # TODO: apply None-coalescing feature to root JsonDeserializable # viz: JsonDeserializable if data is None: return None return cls(type=data["type"], value=data["value"])
class Redelegation(JsonSerializable, JsonDeserializable): __schema__ = S.OBJECT( delegator_address=S.ACC_ADDRESS, validator_src_address=S.VAL_ADDRESS, validator_dst_address=S.VAL_ADDRESS, entries=S.ARRAY(RedelegationEntry.__schema__), ) delegator_address: AccAddress validator_src_address: ValAddress validator_dst_address: ValAddress entries: List[RedelegationEntry] @classmethod def from_data(cls, data: Dict[str, Any]) -> Redelegation: entries = [RedelegationEntry.from_data(re) for re in data["entries"]] return cls( delegator_address=data["delegator_address"], validator_src_address=data["validator_src_address"], validator_dst_address=data["validator_dst_address"], entries=entries, )
class ParamChanges(JsonSerializable, JsonDeserializable): __schema__ = S.ARRAY(ParamChangeSchema) def __init__(self, changes: dict): for k, v in changes.items(): m = symbol.match(k) if not m: raise InvalidParamChange( f"Parameter change subspace could not be parsed: {k}") if not isinstance(v, dict): raise InvalidParamChange( f"Parameter change value should be a dict but got: '{type(v)}' for {k}" ) for sk, sv in v.items(): sm = symbol.match(sk) if not sm: raise InvalidParamChange( f"Parameter change key could not be parsed - {k}: '{sk}'" ) self.changes = JiguBox(changes) def __repr__(self) -> str: return f"<ParamChanges {self.changes!r}>" def __getattr__(self, name: str) -> JiguBox: return self.changes[name] def __getitem__(self, item) -> JiguBox: return self.changes[item] @staticmethod def _get_key(subspace, key, inverse=False): """Each parameter has a special key in the ParamStore that might not correspond to the parameter's JSON-name. We use this function to look up the ParamStore key for the JSON-name, which we are familiar with. Set `inverse` to `True` to look up from the opposite direction for deserialization. """ try: table = PARAMSTORE_KEY_LOOKUP_TABLE[subspace] if inverse: table = table.inverse return table[key] except KeyError: return key @staticmethod def _marshal_value(subspace, key, value): """Formats the value for param change. Terra node expects all the values to be JSON-encoded, and Amino:JSON int/int64/uint/uint64 expects quoted values for JavaScript numeric support, and int16/uint16 need `int`. """ try: if key not in PARAM_DEFNS[subspace]: # if not JSON-name # try paramstore name key = ParamChanges._get_key(subspace, key, inverse=True) if len(PARAM_DEFNS[subspace][key]) == 3: value = PARAM_DEFNS[subspace][key][2](value) except KeyError: pass return serialize_to_json(value) @staticmethod def _unmarshal_value(subspace, key, value): """Looks up the correct type to decode the right type for the parameter change.""" if subspace in DES_LOOKUP_TABLE and key in DES_LOOKUP_TABLE[subspace]: if DES_LOOKUP_TABLE[subspace][key] is not None: return DES_LOOKUP_TABLE[subspace][key](value) return value @property def pretty_data(self): return self.changes.items() def to_data(self) -> list: param_changes = [] for subspace, v in self.changes.items(): for key, value in v.items(): param_changes.append({ "subspace": subspace, "key": self._get_key(subspace, key), "value": self._marshal_value(subspace, key, value), }) return param_changes @classmethod def from_data(cls, data: list) -> ParamChanges: changes = JiguBox(default_box=True) for p in data: subspace = p["subspace"] key = cls._get_key( subspace, p["key"], inverse=True ) # p["key"] is paramstore key, we are using json-name keys inside Jigu value = cls._unmarshal_value(subspace, p["key"], json.loads(p["value"])) changes[subspace][key] = value return cls(changes)
class MsgInfo(wrapt.ObjectProxy): __schema__ = S.OBJECT( msg_index=S.INTEGER, success=S.BOOLEAN, log=S.STRING, events=S.ARRAY( S.OBJECT( type=S.STRING, attributes=S.ARRAY(S.OBJECT(key=S.STRING, value=S.STRING)), )), ) def __init__(self, msg: StdMsg, success: bool, log: dict, events: EventsQuery): wrapt.ObjectProxy.__init__(self, msg) try: log = json.loads(log) if type(log) == dict: log = JiguBox(log) except: log = None self._self_success = success self._self_log = log self._self_events = events self._self_pretty_data = None @property def success(self): return self._self_success @property def log(self): return self._self_log @property def events(self): return self._self_events def __eq__(self, other): return (isinstance(other, MsgInfo) and self.success == other.success and self.log == other.log and self.events == other.events) def __ne__(self, other): # we have to do this because objectproxy uses the underlying wrapped's __neq__ return not self == other @property def pretty_data(self): d = dict(self.__dict__) items = list(d.items()) items.append(("success", self.success)) if self.log: items.append(("log", self.log)) items.append(("events", self.events)) return items def _pretty_output(self, path: str = ""): return PrettyPrintable._pretty_output(self, path) def _pretty_repr_(self, path: str = "") -> str: return PrettyPrintable._pretty_repr_(self, path) @property def _pp(self): """Shortcut for seeing pretty-printing output.""" see(self) return None
class LazyGradedVestingAccount(Account): __schema__ = S.OBJECT( type=S.STRING_WITH_PATTERN(r"^core/LazyGradedVestingAccount\Z"), value=S.OBJECT( BaseVestingAccount=S.OBJECT( BaseAccount=Account.__schema__["properties"]["value"], original_vesting=Coins.__schema__, delegated_free=Coins.__schema__, delegated_vesting=Coins.__schema__, end_time=S.STRING_INTEGER, ), vesting_schedules=S.ARRAY( S.OBJECT(denom=S.STRING, schedules=S.ARRAY(VestingScheduleEntry.__schema__))), ), ) original_vesting: Coins delegated_free: Coins delegated_vesting: Coins end_time: int vesting_schedules: Dict[str, List[VestingScheduleEntry]] def __init__( self, base_account: Account, original_vesting: Coins, delegated_free: Coins, delegated_vesting: Coins, end_time: int, vesting_schedules: Dict[str, List[VestingScheduleEntry]], ): Account.__init__( self, address=base_account.address, coins=base_account.coins, public_key=base_account.public_key, account_number=base_account.account_number, sequence=base_account.sequence, ) self._base_account = base_account self.original_vesting = original_vesting self.delegated_free = delegated_free self.delegated_vesting = delegated_vesting self.end_time = end_time self.vesting_schedules = vesting_schedules def to_data(self): return { "type": "core/LazyGradedVestingAccount", "value": { "BaseVestingAccount": { "BaseAccount": Account.to_data(self._base_account)["value"], "original_vesting": self.original_vesting, "delegated_free": self.delegated_free, "delegated_vesting": self.delegated_vesting, "end_time": str(self.end_time), }, "vesting_schedules": [{ "denom": denom, "schedules": self.vesting_schedules[denom] } for denom in self.vesting_schedules], }, } @property def pretty_data(self): d = dict(self.__dict__) d.pop("_base_account") return d.items() @classmethod def from_data(cls, data: dict) -> LazyGradedVestingAccount: if "type" in data: data = data["value"] # disregard type bva = data[ "BaseVestingAccount"] # value = { bva { ... } vesting_schedules { } } original_vesting = Coins.from_data(bva["original_vesting"]) delegated_free = Coins.from_data(bva["delegated_free"]) delegated_vesting = Coins.from_data(bva["delegated_vesting"]) vesting_schedules = JiguBox({}) for s in data["vesting_schedules"]: vesting_schedules[s["denom"]] = [ VestingScheduleEntry.deserialize(e) for e in s["schedules"] ] return cls( base_account=Account.from_data(bva["BaseAccount"]), original_vesting=original_vesting, delegated_free=delegated_free, delegated_vesting=delegated_vesting, end_time=int(bva["end_time"]), vesting_schedules=vesting_schedules, )
class Coins(JsonSerializable, JsonDeserializable, Generic[T]): __schema__ = S.ARRAY(Coin.__schema__) def __init__(self, coins: Iterable[Coin] = None, **denoms): if coins is None: coins = [] coins = list(coins) + [Coin(d, a) for d, a in denoms.items()] self._cd = dict() for coin in list(coins): if self._cd.get(coin.denom, None) is None: self._cd[coin.denom] = Coin(coin.denom, coin.amount) else: self._cd[coin.denom] = coin + self._cd[coin.denom] def __repr__(self) -> str: rstr = ", ".join(f"{c.denom}={c.amount!r}" for c in self.coins) return f"Coins({rstr})" def __str__(self) -> str: return ", ".join(str(coin) for coin in self.coins) def to_data(self) -> List[Dict[str, str]]: return [coin.to_data() for coin in self.coins] def _pretty_repr_(self, path: str = "") -> str: d = JiguBox({coin.denom: coin.amount for coin in self.coins}) return d._pretty_repr_() def __add__(self, other: Union[Coin, Coins]) -> Coins: if other == 0: return Coins(self.coins) elif isinstance(other, Coins): return Coins(self.coins + other.coins) elif isinstance(other, Coin): return Coins(self.coins + [other]) else: raise TypeError( f"unsupported operand types for +: 'Coins' and '{type(other)}'" ) def __radd__(self, other): return self + other def __mul__(self, other: Union[int, float, Decimal, Dec]) -> Coins: return Coins([coin * other for coin in self.coins]) def __rmul__(self, other) -> Coins: return self * other def __truediv__(self, other: Union[int, float, Decimal, Dec]) -> Coins: return Coins([coin / other for coin in self.coins]) def __floordiv__(self, other: Union[int, float, Decimal, Dec]) -> Coins: return Coins([coin / other for coin in self.coins]) def __eq__(self, other: object) -> bool: if isinstance(other, Coins): return JsonSerializable.__eq__(self, other) elif isinstance(other, list): try: return JsonSerializable.__eq__(self, Coins(other)) except AttributeError: return False else: return False @classmethod def from_data(cls, data: List[Dict[str, str]]) -> Coins: coins = map(Coin.from_data, data) return cls(coins) @property def denoms(self) -> List[str]: return sorted(self._cd.keys()) @property def coins(self) -> List[Coin]: return sorted(self._cd.values(), key=lambda c: c.denom) @property def dec_coins(self) -> Coins: return Coins(c.dec_coin for c in self.coins) @property def int_coins(self) -> Coins: return Coins(c.int_coin for c in self.coins) def filter(self, predicate: Callable[[Coin], bool]) -> Coins: return Coins(c for c in self.coins if predicate(c)) def __iter__(self): return iter(self.coins) def __contains__(self, denom: str) -> bool: return denom in self._cd def __getitem__(self, denom: str) -> Coin: return self._cd[denom] def __getattr__(self, name: str) -> Coin: if name in self.denoms: return self[name] return self.__getattribute__(name)