> For the complete documentation index, see [llms.txt](https://docs.rujira.network/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rujira.network/developers/ruji-product-integration-guides/staking-ruji-brune-tcy.md).

# Staking (RUJI, bRUNE, TCY)

Technical documentation for wallets, portfolio apps, staking dashboards, and user-facing apps integrating staking markets powered by the Rujira staking contract.

This guide is scoped to user-owned staking positions. It does not cover protocol/operator revenue setup, revenue converter administration, or contract `sudo` messages.

### Overview

The Rujira staking contract is generic. A deployed staking market can be configured for RUJI, bRUNE, TCY, or another supported bond token. Each market has its own contract address and config.

A staking market supports two user paths:

| Path            | User sends                | User receives                                | Reward behavior                                                                           |
| --------------- | ------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Account staking | The market's `bond_denom` | Account staking position (non-transferable)  | Rewards accrue as `revenue_denom` and are claimed manually.                               |
| Liquid staking  | The market's `bond_denom` | Liquid staking receipt tokens (transferable) | Rewards are converted into more `bond_denom`, increasing the value of each receipt token. |

Always query the staking contract config before building transactions. The contract tells you which token to stake through `bond_denom` and which reward token account stakers can claim through `revenue_denom`.

#### Existing Staking Markets

Use this table as an integration hint, not as the source of truth. The live contract config should still be queried before every staking flow.

| Market        | Bond denom | Typical display | Liquid receipt denom | Receipt display |
| ------------- | ---------- | --------------- | -------------------- | --------------- |
| RUJI staking  | `x/ruji`   | `RUJI`          | `x/staking-x/ruji`   | `sRUJI`         |
| bRUNE staking | `x/brune`  | `bRUNE`         | `x/staking-x/brune`  | `ybRUNE`        |
| TCY staking   | `tcy`      | `TCY`           | `x/staking-tcy`      | `sTCY`          |

The bank metadata for `x/ruji`, `x/brune`, and the liquid receipt denoms uses 8 display decimals. The `tcy` supply exists on chain, but the bank metadata endpoint may not return a base metadata object for `tcy`; clients should use the chain/app asset registry or a maintained token list as a display fallback.

#### Who This Guide Is For

| Integrator             | Typical integration                                                                                                          |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Wallets                | Show RUJI balance, account stake, pending revenue, liquid receipt balance, and sign bond/claim/withdraw/unbond transactions. |
| Portfolio apps         | Display account positions, pending rewards, liquid share positions, and pool status.                                         |
| Staking UIs            | Provide standard staking and auto-compounding staking flows.                                                                 |
| Card or repayment apps | Let users claim staking revenue and then use the claimed funds in the app's own repayment flow.                              |

#### Prerequisites

* The user needs a THOR/Rujira app-layer address.
* The user needs the staking `bond_denom`; for RUJI staking this is `x/ruji`.
* Account staking rewards are paid in `revenue_denom`, which should be read from contract config.
* Liquid staking receipt token denom is derived from the bond denom: `x/staking-<bond_denom>`.
* All `Uint128` amounts are strings in JSON.

Useful references:

* RUJI token [FAQ](https://docs.rujira.network/how-it-works/frequently-asked-questions/ruji-token)
* Rujira [secured assets](https://docs.rujira.network/developers/secured-assets)
* RUJI bank [metadata](https://api-thorchain.rorcual.xyz/cosmos/bank/v1beta1/denoms_metadata/x%2Fruji)
* Deployment [list](https://rujira.network/developer/deployment)

### Architecture

```
Wallet / staking UI / portfolio app
        |
        | CosmWasm query / execute
        v
Rujira staking contract
        |
        +-- Account staking
        |       +-- bond bond_denom
        |       +-- claim revenue_denom
        |       +-- withdraw bond_denom + claimed rewards
        |
        +-- Liquid staking
                +-- bond bond_denom
                +-- mint x/staking-<bond_denom>
                +-- convert revenue into more bond_denom
                +-- unbond receipt token for proportional bond_denom
```

{% hint style="info" %}
Do not call `settle` directly. It is an internal self-call used by the staking contract after revenue conversion.
{% endhint %}

### Contract Address and Config

The current staking contract addresses should be read from official deployment data. If static addresses are needed in a downstream guide, verify them first against the Rujira deployment page and look for the relevant `rujira-staking` instances.

Model staking markets by contract address:

```ts
type StakingMarket = {
  label: string;
  contract: string;
  expectedBondDenom?: string;
};

const markets: StakingMarket[] = [
  {
    label: "RUJI",
    contract: "thor1...", // Verify from deployment data.
    expectedBondDenom: "x/ruji",
  },
  {
    label: "bRUNE",
    contract: "thor1...", // Verify from deployment data.
    expectedBondDenom: "x/brune",
  },
  {
    label: "TCY",
    contract: "thor1...", // Verify from deployment data.
    expectedBondDenom: "tcy",
  },
];
```

Query config before rendering the staking UI:

```json
{ "config": {} }
```

Example response shape:

```json
{
  "bond_denom": "x/ruji",
  "revenue_denom": "rune",
  "revenue_converter": [
    "thor1converter...",
    "base64_encoded_execute_msg",
    "1000000"
  ],
  "fee": ["0.05", "thor1fees..."]
}
```

| Field               | Meaning for user integrations                                                                                           |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `bond_denom`        | Token users bond into this staking market. Examples: `x/ruji`, `x/brune`, `tcy`.                                        |
| `revenue_denom`     | Token account stakers claim as rewards.                                                                                 |
| `revenue_converter` | Converter used internally to compound liquid staking rewards. Wallet UIs should display or ignore this, not execute it. |
| `fee`               | Optional protocol fee on distributable revenue, shaped as `[percentage, recipient]`.                                    |

Do not hardcode the reward token from product copy. Different staking markets can pay different `revenue_denom` assets, and the live config is the source of truth for transaction builders.

### Staking Concepts

#### Account Staking vs. Liquid Staking

Account staking and liquid staking use the same bond token, but they behave differently.

| Concept           | Account staking                     | Liquid staking                               |
| ----------------- | ----------------------------------- | -------------------------------------------- |
| Execute namespace | `account`                           | `liquid`                                     |
| User sends        | `bond_denom`                        | `bond_denom`                                 |
| User receives     | Stored account position             | Receipt token balance                        |
| Rewards           | Claimed manually as `revenue_denom` | Converted into more `bond_denom` in the pool |
| Exit              | `account.withdraw`                  | `liquid.unbond` with receipt token funds     |

#### Liquid Receipt Denom

The liquid receipt denom is:

```
x/staking-<bond_denom>
```

Examples:

| Bond denom | Receipt denom       |
| ---------- | ------------------- |
| `x/ruji`   | `x/staking-x/ruji`  |
| `x/brune`  | `x/staking-x/brune` |
| `tcy`      | `x/staking-tcy`     |

Derive this from config in code:

```ts
function receiptDenom(bondDenom: string): string {
  return `x/staking-${bondDenom}`;
}
```

Do not assume the receipt denom from the display symbol. Use the bank denom.

#### Revenue Distribution Timing

Revenue distribution is lazy. Sending `revenue_denom` to the staking contract does not immediately update every account. The contract distributes pending revenue at the start of the next account or liquid staking execute.

For UI, this means:

* `status` shows the current stored state plus raw undistributed revenue.
* `account.pending_revenue` can change after any staking action triggers distribution.
* Re-query `status`, `account`, and bank balances after every staking transaction.

### Query Integration

#### Query Status

```json
{ "status": {} }
```

Response shape:

```json
{
  "account_bond": "100000000",
  "assigned_revenue": "5000000",
  "liquid_bond_shares": "75000000",
  "liquid_bond_size": "78000000",
  "undistributed_revenue": "1000000"
}
```

| Field                   | Meaning                                                                        |
| ----------------------- | ------------------------------------------------------------------------------ |
| `account_bond`          | Total `bond_denom` bonded in account staking.                                  |
| `assigned_revenue`      | Total `revenue_denom` already assigned to account stakers but not yet claimed. |
| `liquid_bond_shares`    | Total liquid receipt shares issued.                                            |
| `liquid_bond_size`      | Total `bond_denom` backing the liquid staking pool.                            |
| `undistributed_revenue` | `revenue_denom` currently in the contract but not yet distributed.             |

For liquid staking display, the simple pool ratio is:

```
bond_per_share = liquid_bond_size / liquid_bond_shares
```

If `liquid_bond_shares` is zero, show the ratio as zero or unavailable.

#### Query Account

```json
{
  "account": {
    "addr": "thor1user..."
  }
}
```

Response shape:

```json
{
  "addr": "thor1user...",
  "bonded": "100000000",
  "pending_revenue": "2500000"
}
```

`account` only works for addresses that have an account staking record. If the query returns not found, show a zero account position:

```json
{
  "addr": "thor1user...",
  "bonded": "0",
  "pending_revenue": "0"
}
```

There is no account-list query and no pagination in the staking contract.

### Account Staking Integration

Account staking is the standard staking path where the user bonds the market token and manually claims rewards.

#### Bond

```json
{
  "account": {
    "bond": {}
  }
}
```

Funds:

```json
[
  { "denom": "<config.bond_denom>", "amount": "100000000" }
]
```

Use `config.bond_denom` for the funds denom. Send only the bond denom.

If the account already has pending rewards, `bond` claims those rewards first and sends them to the user before increasing the bonded amount.

#### Claim

```json
{
  "account": {
    "claim": {}
  }
}
```

Claim is nonpayable. Send no funds.

If there are claimable rewards, the contract sends `revenue_denom` to the user. If the user already has an account staking record and rewards are zero, the transaction can still complete and emit a zero-amount claim event. If the user has never bonded, do not offer claim; there is no account record yet.

#### Withdraw

Partial withdraw:

```json
{
  "account": {
    "withdraw": {
      "amount": "50000000"
    }
  }
}
```

Full withdraw:

```json
{
  "account": {
    "withdraw": {
      "amount": null
    }
  }
}
```

Withdraw is nonpayable. Send no funds.

Withdraw requires an existing account staking record. It always claims pending rewards first. The payout can include both `bond_denom` and `revenue_denom`.

#### TypeScript Example: Account Bond

```ts
import { CosmWasmClient, SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";

const rpc = "See Developer Endpoints";
const sender = "thor1...";
const amount = "100000000"; // Base units for the selected bond denom.

const market = {
  label: "bRUNE",
  contract: "thor1...", // Verify from deployment data.
  expectedBondDenom: "x/brune",
};

const stakingContract = market.contract;

const queryClient = await CosmWasmClient.connect(rpc);

const config = await queryClient.queryContractSmart(stakingContract, {
  config: {},
});

if (market.expectedBondDenom && config.bond_denom !== market.expectedBondDenom) {
  throw new Error(`Unexpected bond denom: ${config.bond_denom}`);
}

const signingClient = await SigningCosmWasmClient.connectWithSigner(rpc, signer);

const tx = await signingClient.execute(
  sender,
  stakingContract,
  {
    account: {
      bond: {},
    },
  },
  "auto",
  "",
  [{ denom: config.bond_denom, amount }],
);
```

#### TypeScript Example: Claim and Use Rewards

This is the wallet/user flow a card or repayment app should use. The staking contract claims rewards to the user first; the app then uses the user's resulting `revenue_denom` balance in its own repayment flow.

```ts
async function queryAccountOrZero(addr: string) {
  try {
    return await queryClient.queryContractSmart(stakingContract, {
      account: {
        addr,
      },
    });
  } catch (error) {
    if (String(error).toLowerCase().includes("not found")) {
      return {
        addr,
        bonded: "0",
        pending_revenue: "0",
      };
    }

    throw error;
  }
}

const before = await queryAccountOrZero(sender);

if (BigInt(before.pending_revenue) > 0n) {
  await signingClient.execute(
    sender,
    stakingContract,
    {
      account: {
        claim: {},
      },
    },
    "auto",
    "",
    [],
  );
}

// After the claim is indexed, query the user's revenue_denom bank balance
// and use that balance in the app's own repayment transaction.
```

### Liquid Staking Integration

Liquid staking is the auto-compounding path. The user bonds the market token and receives receipt tokens. As revenue is converted into more `bond_denom`, the pool size grows and each receipt token represents more underlying bond token.

#### Bond

```json
{
  "liquid": {
    "bond": {}
  }
}
```

Funds:

```json
[
  { "denom": "<config.bond_denom>", "amount": "100000000" }
]
```

The contract mints receipt tokens to the user. Derive the receipt denom from config:

```
receipt_denom = x/staking-<config.bond_denom>
```

#### Unbond

```json
{
  "liquid": {
    "unbond": {}
  }
}
```

Funds:

```json
[
  { "denom": "<receipt_denom>", "amount": "100000000" }
]
```

Liquid unbond burns receipt tokens and returns the user's proportional `bond_denom`. The returned amount depends on the current pool ratio and is floor-rounded by share-pool math.

#### TypeScript Example: Liquid Bond and Unbond

```ts
function liquidReceiptDenom(bondDenom: string): string {
  return `x/staking-${bondDenom}`;
}

const config = await queryClient.queryContractSmart(stakingContract, {
  config: {},
});

const receipt = liquidReceiptDenom(config.bond_denom);

await signingClient.execute(
  sender,
  stakingContract,
  {
    liquid: {
      bond: {},
    },
  },
  "auto",
  "",
  [{ denom: config.bond_denom, amount: "100000000" }],
);

await signingClient.execute(
  sender,
  stakingContract,
  {
    liquid: {
      unbond: {},
    },
  },
  "auto",
  "",
  [{ denom: receipt, amount: "50000000" }],
);
```

### Events

CosmWasm event types usually appear in indexed transaction logs with a `wasm-` prefix.

Account staking events:

```
wasm-rujira-staking/account.bond
wasm-rujira-staking/account.claim
wasm-rujira-staking/account.withdraw
```

| Event              | Attributes                   |
| ------------------ | ---------------------------- |
| `account.bond`     | `owner`, `amount`            |
| `account.claim`    | `owner`, `amount`            |
| `account.withdraw` | `owner`, `amount`, `rewards` |

Liquid staking events:

```
wasm-rujira-staking/liquid.bond
wasm-rujira-staking/liquid.unbond
```

| Event           | Attributes                    |
| --------------- | ----------------------------- |
| `liquid.bond`   | `owner`, `amount`, `shares`   |
| `liquid.unbond` | `owner`, `shares`, `returned` |

Internal settlement event:

```
wasm-rujira-staking/settle
```

`settle` has a `returned` attribute and is emitted by the contract's internal revenue-conversion flow. User interfaces can index it, but users should not build or sign `settle` messages.

Use events for fast UI updates, but re-query contract state and bank balances after the transaction is indexed.

### UI and UX Checklist

* Show account staking and liquid staking as separate modes.
* Query `config` first and use its `bond_denom` and `revenue_denom`.
* Display the bond token from bank metadata or a maintained token registry. Do not hardcode a single staking denom.
* For TCY, handle metadata fallback because `tcy` may not return a base bank metadata object even though the denom exists on chain.
* For account staking, show `bonded` and `pending_revenue`.
* For account staking, explain that rewards are claimable manually in `revenue_denom`.
* For liquid staking, show receipt token balance and estimated underlying `bond_denom`.
* For liquid staking, explain that rewards compound by increasing pool value, not by sending revenue directly to the user.
* For card or repayment apps, claim rewards first, then use the user's claimed balance in the app repayment flow.
* Prevent users from attaching funds to `claim` or `withdraw`.
* Prevent users from sending `config.bond_denom` to `liquid.unbond`; it requires the receipt token.
* Refresh `status`, `account`, and bank balances after every transaction.

### Common Errors and Gotchas

| Issue                                                   | Why it happens                                           | Integrator fix                                                     |
| ------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------ |
| Wrong funds denom on bond                               | Account and liquid bond require `bond_denom`.            | Query `config` and send only `config.bond_denom`.                  |
| Claim sent with funds                                   | `account.claim` is nonpayable.                           | Send no funds.                                                     |
| Withdraw sent with funds                                | `account.withdraw` is nonpayable.                        | Send no funds.                                                     |
| Liquid unbond sends the bond token                      | `liquid.unbond` burns receipt tokens, not bond tokens.   | Send `x/staking-<bond_denom>` funds.                               |
| Account query fails for a new user                      | The account record does not exist until the user bonds.  | Treat not found as zero bonded and zero pending revenue.           |
| Claim or withdraw fails for a new user                  | There is no account staking record yet.                  | Disable claim and withdraw until the user has an account position. |
| Pending rewards look stale                              | Distribution runs lazily during staking executes.        | Re-query after transactions and explain update timing.             |
| User expects liquid rewards as the account reward token | Liquid rewards are converted into more bond token value. | Show pool ratio and receipt-token underlying value.                |
| Tiny liquid bond mints zero shares                      | Share math floors issuance.                              | Block very small deposits or explain the failure.                  |
| Unbond amount exceeds receipt balance                   | Share pool rejects over-unbonding.                       | Check the user's receipt token balance before signing.             |

### Message Reference

#### Queries

```json
{ "config": {} }
```

```json
{ "status": {} }
```

```json
{ "account": { "addr": "thor1user..." } }
```

#### Executes

```json
{ "account": { "bond": {} } }
```

```json
{ "account": { "claim": {} } }
```

```json
{ "account": { "withdraw": { "amount": "1000000" } } }
```

```json
{ "account": { "withdraw": { "amount": null } } }
```

```json
{ "liquid": { "bond": {} } }
```

```json
{ "liquid": { "unbond": {} } }
```

Do **not** use this internal message in wallet or user integrations:

```json
{ "settle": { "balance": "0" } }
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.rujira.network/developers/ruji-product-integration-guides/staking-ruji-brune-tcy.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
