Skip to main content
AcceptQuote is the onchain entrypoint that settles an RFQ trade. As a taker, it’s the only contract message you call. This page documents every field, every encoding rule, and every validation the contract performs – including the features that aren’t obvious from the other docs.

Message shape

{
  "accept_quote": {
    "rfq_id": 1708000700000,
    "market_id": "0xdc70164d7120529c3cd84278c98df4151210c0447a65a2aab03459cf328de41e",
    "direction": "long",
    "margin": "200",
    "quantity": "100",
    "worst_price": "5.00",
    "quotes": [
      {
        "maker": "inj1maker...",
        "margin": "200",
        "quantity": "100",
        "price": "4.95",
        "expiry": { "ts": 1708000800000 },
        "signature": "Kg8z...base64..."
      }
    ],
    "unfilled_action": null
  }
}
This JSON is the payload of a standard MsgExecuteContract transaction to the TrueCurrent contract address, signed by the taker’s private key.

Three encoding rules you must follow

These encoding rules are relatively common to get wrong. However, they are strictly enforced, as the contract parses the message with strict Rust serde types.

1. rfq_id is a JSON number, not a string

The contract field is u64. JSON encoders that stringify large integers will produce bytes the contract rejects with a deserialization error.
"rfq_id": 1708000700000     // ✅ correct
"rfq_id": "1708000700000"   // ❌ will fail to parse
In TypeScript, Date.now() returns a number – use it directly. In Python, cast int(rfq_id) before serializing; if you build the request from a string, convert first.

2. Quote expiry must be wrapped as {"ts": <ms>}

The contract’s Expiry type is an enum with two variants – timestamp or block height. Serde requires the variant tag:
"expiry": { "ts": 1708000800000 }   // ✅ timestamp variant (Unix ms)
"expiry": { "h": 19500000 }          // ✅ block height variant (rarely used)
"expiry": 1708000800000              // ❌ raw int is not a valid Expiry
The indexer delivers expiry as a plain integer. You must wrap it before passing to the contract.

3. Quote signature is base64, not hex

Indexers (including TrueCurrent’s) transmit signatures as hex strings ("0xabc123..."). The contract’s signature field is a CosmWasm Binary type, which on the wire is standard base64. You must convert. Python:
import base64

sig_hex = quote["signature"]
if sig_hex.startswith("0x"):
    sig_hex = sig_hex[2:]
signature_b64 = base64.b64encode(bytes.fromhex(sig_hex)).decode("utf-8")
TypeScript:
const sigHex = quote.signature.replace(/^0x/, "");
const signatureB64 = Buffer.from(sigHex, "hex").toString("base64");
If you skip the conversion, the contract returns a signature verification error that looks like the maker signed badly. However the true cause of the error is encoding. The ContractClient.accept_quote() helper in injective-rfq-toolkit handles all three of these automatically. If you’re building from scratch, apply them yourself.

Field reference

Top-level fields:
FieldTypeRequiredDescription
rfq_idnumber (u64)yesMust match the rfq_id on the request you sent to the indexer. Used as a taker-scoped nonce to prevent replay.
market_idstringyesInjective derivative market hex ID
directionstringyes"long" or "short" – lowercase. This is the taker’s direction.
marginstringyesTaker margin in USDC, decimal string
quantitystringyesRequested quantity
worst_pricestringyesHard price limit. Individual quotes worse than this are rejected.
quotesarrayyesOne or more maker quotes to consume.
unfilled_actionnullnoReserved field; pass null. See Unfilled action below.
subaccount_noncenumber | nullnoSubaccount index for the taker. Defaults to 0.
cidstring | nullnoClient identifier echoed in the settlement event, for your own tracking.
Per-quote fields:
FieldTypeRequiredDescription
makerstringyesMaker’s inj1... address
marginstringyesMaker’s margin commitment
quantitystringyesQuantity the maker is offering
pricestringyesMaker’s quoted price
expiry{ts: number} or {h: number}yesQuote expiry – wrapped enum
signaturestring (base64)yesMaker’s signature over the canonical quote payload
noncenumber | nullnoReserved for non-standard quote paths; normal RFQ quotes omit it
min_fill_quantitystring | nullnoMaker-imposed minimum fill; if the contract would fill them for less, the quote is skipped

Single-quote settlement

The simplest case: you collect quotes, pick one, accept it. Python – high-level helper:
from rfq_test.clients.contract import ContractClient
from rfq_test.models.types import Direction
from decimal import Decimal

contract = ContractClient(config.contract, config.chain)

tx_hash = await contract.accept_quote(
    private_key=TAKER_PRIVATE_KEY,
    quotes=[{
        "maker": best["maker"],
        "margin": "200",
        "quantity": "100",
        "price": best["price"],
        "expiry": best["expiry"],            # int ms – client wraps {"ts": ...}
        "signature": best["signature"],      # hex – client converts to base64
    }],
    rfq_id=str(rfq_id),
    market_id=MARKET_ID,
    direction=Direction.LONG,
    margin=Decimal("200"),
    quantity=Decimal("100"),
    worst_price=Decimal("5.00"),
    unfilled_action=None,
)
Python – manual, no helper:
import base64, json
from pyinjective.composer_v2 import Composer
from pyinjective.core.broadcaster import MsgBroadcasterWithPk

sig_hex = best["signature"].removeprefix("0x")
sig_b64 = base64.b64encode(bytes.fromhex(sig_hex)).decode()

msg = {
    "accept_quote": {
        "rfq_id": int(rfq_id),                       # number
        "market_id": MARKET_ID,
        "direction": "long",                         # lowercase
        "margin": "200",
        "quantity": "100",
        "worst_price": "5.00",
        "quotes": [{
            "maker": best["maker"],
            "margin": "200",
            "quantity": "100",
            "price": best["price"],
            "expiry": {"ts": int(best["expiry"])},   # wrapped
            "signature": sig_b64,                    # base64
        }],
        "unfilled_action": None,
    }
}

composer = Composer(network=network.string())
execute = composer.msg_execute_contract(
    sender=taker_inj_address,
    contract=CONTRACT_ADDRESS,
    msg=json.dumps(msg, ensure_ascii=False),
)
result = await broadcaster.broadcast([execute])
TypeScript:
import {
  MsgExecuteContractCompat,
  MsgBroadcasterWithPk,
} from "@injectivelabs/sdk-ts";
import { Network } from "@injectivelabs/networks";

const sigB64 = Buffer.from(
  best.signature.replace(/^0x/, ""),
  "hex",
).toString("base64");

const msg = MsgExecuteContractCompat.fromJSON({
  sender: takerInjAddress,
  contractAddress: CONTRACT_ADDRESS,
  msg: {
    accept_quote: {
      rfq_id: rfqId,                           // number
      market_id: MARKET_ID,
      direction: "long",                       // lowercase
      margin: "200",
      quantity: "100",
      worst_price: "5.00",
      quotes: [
        {
          maker: best.maker,
          margin: "200",
          quantity: "100",
          price: best.price,
          expiry: { ts: best.expiry },         // wrapped
          signature: sigB64,                   // base64
        },
      ],
      unfilled_action: null,
    },
  },
});

const broadcaster = new MsgBroadcasterWithPk({
  privateKey: TAKER_PRIVATE_KEY,
  network: Network.TestnetSentry,
});

const { txHash } = await broadcaster.broadcast({ msgs: msg });

Multi-quote aggregation

The quotes field is an array. You can submit multiple quotes from different makers in a single AcceptQuote call, and the contract will consume them sequentially until your quantity is filled. This is the main mechanism for getting size done when no single maker can cover your whole request. Scenario: you want to go long 100 INJ. Three makers respond:
MakerQuantityPrice
Alice404.90
Bob404.92
Carol504.95
Submit all three in one transaction, sorted by price ascending (because you’re a buyer and want the cheapest fills first):
quotes = [
    alice_quote,   # 40 @ 4.90
    bob_quote,     # 40 @ 4.92
    carol_quote,   # 50 @ 4.95
]

tx_hash = await contract.accept_quote(
    private_key=TAKER_PRIVATE_KEY,
    quotes=quotes,
    rfq_id=str(rfq_id),
    market_id=MARKET_ID,
    direction=Direction.LONG,
    margin=Decimal("200"),
    quantity=Decimal("100"),
    worst_price=Decimal("5.00"),
    unfilled_action=None,
)
The contract walks the array:
  1. Fill 40 from Alice at 4.90 → remaining 60
  2. Fill 40 from Bob at 4.92 → remaining 20
  3. Fill 20 from Carol at 4.95 → remaining 0 (partial consumption of Carol’s quote – legal and expected)
  4. Done. Your position opens with a volume-weighted entry.
You end up with a single taker position for 100 INJ at a blended entry. Each maker gets a maker-side position for the quantity they actually filled.
Important: quote order matters. The contract processes the quotes array in submission order, not by price. If you submit Carol first, the contract happily takes 50 at 4.95, then Bob’s 40 at 4.92, then only 10 from Alice – you end up with a worse blended price. Therefore, always sort your quotes by price before submitting. For long sort quotes by ascending price. For shorts, sort quotes by descending price.
There is a maximum. The contract enforces quotes.len() <= config.max_quotes. In practice this is approximately 20. Check the current value with a config query to obtain the exact number before submitting very long lists.

Unfilled action

"unfilled_action": null
The current TrueCurrent product is RFQ-only. If your submitted quotes don’t cover the full quantity, the quote still settles the portion that was filled — the remainder is simply not traded. If zero quotes fill (e.g. all were rejected by signature or expiry), the quote fails. Pass null for unfilled_action on every call. The contract field exists for future use; non-null values are not exposed in the public product today.

On-chain validation

For each quote in the submitted array, the contract performs the following checks in order. If a quote fails any check, it is skipped (with an entry in quote_results), and the loop continues to the next quote.
CheckFailure mode
Quote expiry not passedSkip with “quote expired”
Maker is currently whitelistedSkip with “unknown maker”
Maker has not already used this (taker, rfq_id) nonceSkip with “nonce replay”
Signature verifies against canonical quote payloadSkip with “signature mismatch”
Quote price is within taker’s worst_priceSkip with “price exceeds worst_price”
Maker has sufficient available balance for their marginSkip with “insufficient maker balance”
Filling this quote wouldn’t fall below maker’s min_fill_quantitySkip with “below min fill”
Quote direction matches (implicit from signature)Skip with “direction mismatch”
After the loop, the contract checks:
  • At least some fills happened – if filled_quantity == 0, the whole quote fails with all quotes rejected. If at least one quote filled, the quote settles the filled quantity and the remainder is simply not traded.
  • Taker has enough balance for the aggregate margin used across all filled quotes.
  • quotes.len() <= config.max_quotes – checked at the top of the handler before any iteration.
The full list of per-quote failures is available in the emitted settlement event under quote_results, keyed by maker address. Inspect this list in a failed quote to diagnose exactly which quote was rejected and why.

Post-settlement

After a successful quote:
  • You have a new position in your Injective exchange subaccount. Query it via the standard exchange module APIs – there is no RFQ-specific position state onchain.
  • The settlement event contains the filled quantity, the per-quote quote_results, the taker’s aggregate entry price, and the cid you passed (useful for matching events to your trade log).
  • Fees have been deducted according to the current taker_fee_rate and maker_fee_rate in the contract’s config. Query config for the current values.

Querying the settlement history

The indexer maintains a history of settlements indexed by taker address. Query it via HTTP:
POST https://testnet.rfq.injective.network/api/rfq/v1/list-settlement
Content-Type: application/json

{
  "pagination": { "offset": 0, "limit": 100 },
  "addresses": ["inj1taker..."]
}
Each entry includes the rfq_id, tx_hash, fill quantities, per-quote results, and timestamps. Use this to reconcile your client-side trade log with onchain reality.

Next

Last modified on June 2, 2026