Reference
Harpoon Module Reference
The Harpoon module allows CosmWasm smart contracts to subscribe to staking module hooks for tracking historical voting power and other staking events.
Messages
MsgManageHookSubscription
Manages hook subscriptions for a contract address. Can only be executed by the module's authority (governance).
service Msg {
option (cosmos.msg.v1.service) = true;
// Updates hook subscriptions for a specific contract address.
// To remove a subscription, pass an empty array to `hook_subscription.hooks`.
// Can only be executed by the module's authority.
rpc ManageHookSubscription(MsgManageHookSubscription) returns (MsgManageHookSubscriptionResponse);
}
message MsgManageHookSubscription {
option (cosmos.msg.v1.signer) = "authority";
option (amino.name) = "harpoon/MsgManageHookSubscription";
// Address of the governance account
string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// Hook subscription to be updated
HookSubscription hook_subscription = 2;
}
message MsgManageHookSubscriptionResponse {}
message HookSubscription {
// Contract address to update subscriptions for
string contract_address = 2;
// List of hooks to subscribe to. Hooks not listed here will be removed.
repeated HookType hooks = 3;
}
Authority Required: Governance only
Behavior:
- Adds the contract to specified hook types
- Removes the contract from hook types not in the list
- Passing an empty
hooksarray removes all subscriptions for the contract
Queries
SubscribedContracts
Retrieves contracts subscribed to a specific hook type.
service Query {
// Retrieves contracts subscribed to a specific hook type.
rpc SubscribedContracts(QuerySubscribedContractsRequest) returns (QuerySubscribedContractsResponse) {
option (google.api.http).get = "/neutron/harpoon/subscribed_contracts";
}
}
message QuerySubscribedContractsRequest {
// The response will include only contract addresses for this hook type
HookType hook_type = 1;
}
message QuerySubscribedContractsResponse {
// List of contract addresses subscribed to a specific hook
repeated string contract_addresses = 1;
}
REST Endpoint: GET /neutron/harpoon/subscribed_contracts
CLI Commands
Query Commands
subscribed-contracts
Lists all contracts subscribed to a given hook type.
neutrond query harpoon subscribed-contracts [hook-type]
Parameters:
hook-type: The specific hook type to query (e.g.,HOOK_TYPE_AFTER_VALIDATOR_CREATED)
Example:
neutrond query harpoon subscribed-contracts HOOK_TYPE_AFTER_VALIDATOR_CREATED
Hook Types
Available staking hooks that contracts can subscribe to:
enum HookType {
option (gogoproto.goproto_enum_prefix) = false;
HOOK_TYPE_UNSPECIFIED = 0;
// Triggered after validator is created
HOOK_TYPE_AFTER_VALIDATOR_CREATED = 1;
// Triggered before validator is modified
HOOK_TYPE_BEFORE_VALIDATOR_MODIFIED = 2;
// Triggered after validator is removed
HOOK_TYPE_AFTER_VALIDATOR_REMOVED = 3;
// Triggered after validator is bonded
HOOK_TYPE_AFTER_VALIDATOR_BONDED = 4;
// Triggered after validator begins unbonding
HOOK_TYPE_AFTER_VALIDATOR_BEGIN_UNBONDING = 5;
// Triggered before delegation is created
HOOK_TYPE_BEFORE_DELEGATION_CREATED = 6;
// Triggered before delegation's shares are modified
HOOK_TYPE_BEFORE_DELEGATION_SHARES_MODIFIED = 7;
// Triggered before delegation is removed
HOOK_TYPE_BEFORE_DELEGATION_REMOVED = 8;
// Triggered after delegation is modified
HOOK_TYPE_AFTER_DELEGATION_MODIFIED = 9;
// Triggered before validator is slashed
HOOK_TYPE_BEFORE_VALIDATOR_SLASHED = 10;
// Triggered after unbonding is initiated
HOOK_TYPE_AFTER_UNBONDING_INITIATED = 11;
}
Storage
Hook Subscriptions
The module stores subscriptions with the following structure:
message HookSubscriptions {
// The hook type being subscribed to
HookType hook_type = 1;
// Contract addresses subscribed to this hook type
repeated string contract_addresses = 2;
}
Storage Layout:
KEY: []byte("subscriptions") + BigEndian(uint32(hookType))
VALUE: HookSubscriptions (marshaled protobuf)
This layout optimizes for the most frequent operation: retrieving all contracts subscribed to a specific hook type.
Genesis
The module's genesis state contains all hook subscriptions:
message GenesisState {
// List of hooks
repeated HookSubscriptions hook_subscriptions = 1 [(gogoproto.nullable) = false];
}
Module Constants
- ModuleName:
"harpoon" - StoreKey:
"harpoon" - RouterKey:
"harpoon"
Sudo Message Formats
When hooks are triggered, contracts receive specific message formats:
Validator Hooks
// AfterValidatorCreated
{
"after_validator_created": {
"val_addr": "neutronvaloper1..."
}
}
// BeforeValidatorModified
{
"before_validator_modified": {
"val_addr": "neutronvaloper1..."
}
}
// AfterValidatorRemoved
{
"after_validator_removed": {
"cons_addr": "neutronvalcons1...",
"val_addr": "neutronvaloper1..."
}
}
// AfterValidatorBonded
{
"after_validator_bonded": {
"cons_addr": "neutronvalcons1...",
"val_addr": "neutronvaloper1..."
}
}
// AfterValidatorBeginUnbonding
{
"after_validator_begin_unbonding": {
"cons_addr": "neutronvalcons1...",
"val_addr": "neutronvaloper1..."
}
}
// BeforeValidatorSlashed
{
"before_validator_slashed": {
"val_addr": "neutronvaloper1...",
"fraction": "0.050000000000000000",
"tokens_to_burn": "1000000"
}
}
Delegation Hooks
// BeforeDelegationCreated
{
"before_delegation_created": {
"del_addr": "neutron1...",
"val_addr": "neutronvaloper1..."
}
}
// BeforeDelegationSharesModified
{
"before_delegation_shares_modified": {
"del_addr": "neutron1...",
"val_addr": "neutronvaloper1..."
}
}
// BeforeDelegationRemoved
{
"before_delegation_removed": {
"del_addr": "neutron1...",
"val_addr": "neutronvaloper1..."
}
}
// AfterDelegationModified
{
"after_delegation_modified": {
"del_addr": "neutron1...",
"val_addr": "neutronvaloper1..."
}
}
Unbonding Hooks
// AfterUnbondingInitiated
{
"after_unbonding_initiated": {
"id": 12345
}
}
Key Implementation Details
- No Cached Context: Sudo calls use the original context, ensuring state consistency
- Error Propagation: Contract errors can abort transactions or halt the chain
- Governance-Only: Only governance can manage hook subscriptions
- Special Slashing Hook:
BeforeValidatorSlashedpanics;BeforeValidatorSlashedWithTokensToBurnis used instead - Subscription Management: Adding/removing hooks is done atomically per contract