> For the complete documentation index, see [llms.txt](https://docs.monday.trade/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.monday.trade/rwa-trading-apis/guides/demo-code.md).

# Demo Code

TypeScript examples for RWA API integration. HMAC authentication uses Node `crypto`; on-chain actions use [viem](https://viem.sh).

```bash
npm install viem
```

Set environment variables:

```bash
export TRADING_API_KEY="your-api-key"
export TRADING_API_SECRET="your-api-secret"
export TRADING_USER_PRIVATE_KEY="0x..."   # self-submit / One Click user wallet
```

## API client with HMAC

```ts
import crypto from "node:crypto";

const BASE_URL = "https://mainnet-api.monday.trade/rwa/trading";

type HttpMethod = "GET" | "POST" | "DELETE";

interface TradingApiConfig {
    apiKey: string;
    apiSecret: string;
    chainId: number;
    productType: "MondayTrade";
}

function canonicalUri(path: string, query: Record<string, string | string[] | undefined> = {}): string {
    const pairs: string[] = [];
    for (const key of Object.keys(query).sort()) {
        const value = query[key];
        if (value === undefined || value === null) continue;
        if (Array.isArray(value)) {
            for (const item of value) pairs.push(`${key}=${item}`);
        } else {
            pairs.push(`${key}=${value}`);
        }
    }
    return pairs.length === 0 ? path : `${path}?${pairs.join("&")}`;
}

function signTradingApi(
    apiSecret: string,
    method: HttpMethod,
    path: string,
    query: Record<string, string | string[] | undefined>,
    timestamp: number,
    nonce: string,
    rawBody: string,
): string {
    const uri = canonicalUri(path, query);
    const payload = [method, uri, String(timestamp), nonce, rawBody].join("\n");
    return crypto.createHmac("sha256", apiSecret).update(payload, "utf8").digest("hex");
}

async function tradingApiRequest<T>(
    config: TradingApiConfig,
    method: HttpMethod,
    path: string,
    options: {
        query?: Record<string, string | string[] | undefined>;
        body?: unknown;
    } = {},
): Promise<T> {
    const query = options.query ?? {};
    const rawBody = options.body === undefined ? "" : JSON.stringify(options.body);
    const timestamp = Date.now();
    const nonce = crypto.randomUUID();
    const signature = signTradingApi(config.apiSecret, method, path, query, timestamp, nonce, rawBody);

    const url = new URL(`${BASE_URL}${canonicalUri(path, query)}`);
    const response = await fetch(url, {
        method,
        headers: {
            "content-type": "application/json",
            "x-api-key": config.apiKey,
            "x-api-ts": String(timestamp),
            "x-api-nonce": nonce,
            "x-api-sign": signature,
            "x-api-chain-id": String(config.chainId),
            "x-api-p": config.productType,
        },
        body: method === "GET" || method === "DELETE" ? undefined : rawBody,
    });

    const json = await response.json() as { code?: number; errMsg?: string; data?: T };
    if (json.code !== 200) {
        throw new Error(json.errMsg || `API error ${json.code}`);
    }
    return json.data as T;
}
```

## Self-submit market buy (viem)

Build calldata from the API, broadcast with viem, then record the tx hash.

```ts
import { createPublicClient, createWalletClient, http, type Address, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";

interface OrderCalldata {
    chainId: number;
    productType: string;
    toAddress: Address;
    value: string;
    callData: Hex;
    method: string;
}

const config: TradingApiConfig = {
    apiKey: process.env.TRADING_API_KEY!,
    apiSecret: process.env.TRADING_API_SECRET!,
    chainId: 8453,
    productType: "MondayTrade",
};

const account = privateKeyToAccount(process.env.TRADING_USER_PRIVATE_KEY as Hex);
const walletClient = createWalletClient({
    account,
    chain: base,
    transport: http(),
});
const publicClient = createPublicClient({ chain: base, transport: http() });

// Router calls under-estimate gas on some chains (e.g. Monad): the default
// estimate can leave the tx short and it reverts out-of-gas. Add a buffer.
async function submitTx(to: Address, callData: Hex, value: string): Promise<Hex> {
    const gas = (await publicClient.estimateGas({
        account: account.address,
        to,
        data: callData,
        value: BigInt(value),
    })) * 2n;
    return walletClient.sendTransaction({ to, data: callData, value: BigInt(value), gas });
}

async function placeMarketBuySelfSubmit(stockAddress: Address, notional: string): Promise<Hex> {
    const calldata = await tradingApiRequest<OrderCalldata>(config, "POST", "/api/v1/orders/calldata", {
        body: {
            stockAddress,
            side: "Buy",
            type: "Market",
            notional,
            deadline: Math.floor(Date.now() / 1000) + 3600,
        },
    });

    const txHash = await submitTx(calldata.toAddress, calldata.callData, calldata.value);

    await tradingApiRequest(config, "POST", "/api/v1/orders/tx", {
        body: { txHash },
    });

    return txHash;
}
```

## Deposit with ERC-20 approve (viem)

Approve the spender, then self-submit deposit calldata.

```ts
import { erc20Abi, maxUint256, type Address, type Hex } from "viem";

// Reuses the module-level `walletClient`, `publicClient`, `account`, `config`,
// and `submitTx` defined above.
const SPENDER: Address = "0x4f090d817fd83753988a7b0c1d76f170f8461be8"; // MondayTrade mainnet StockRouter

interface CashCalldata {
    chainId: number;
    productType: string;
    toAddress: Address;
    value: string;
    callData: Hex;
    method: string;
}

async function depositSelfSubmit(tokenAddress: Address, tokenAmount: string): Promise<Hex> {
    const approveHash = await walletClient.writeContract({
        address: tokenAddress,
        abi: erc20Abi,
        functionName: "approve",
        args: [SPENDER, maxUint256],
    });
    await publicClient.waitForTransactionReceipt({ hash: approveHash });

    const calldata = await tradingApiRequest<CashCalldata>(config, "POST", "/api/v1/cash/deposits/calldata", {
        body: {
            userAddress: account.address,
            tokenAddress,
            tokenAmount,
        },
    });

    const txHash = await submitTx(calldata.toAddress, calldata.callData, calldata.value);

    await tradingApiRequest(config, "POST", "/api/v1/cash/deposits/tx", {
        body: { txHash },
    });

    return txHash;
}
```

Spender addresses: [cash/deposits.md](/rwa-trading-apis/cash-operations/deposits.md).

## Order with deposit (self-submit)

Use `/orders/with-deposit/calldata` when the same transaction should deposit an approved token and place an order. For buy orders, the cash deposit must be eligible for instant deposit. The token must be approved before submitting the returned calldata.

```ts
async function placeMarketBuyWithDepositSelfSubmit(
    cashTokenAddress: Address,
    stockAddress: Address,
    depositAmount: string,
    notional: string,
): Promise<Hex> {
    const calldata = await tradingApiRequest<OrderCalldata>(config, "POST", "/api/v1/orders/with-deposit/calldata", {
        body: {
            stockAddress,
            depositTokenAddress: cashTokenAddress,
            depositAmount,
            side: "Buy",
            type: "Market",
            notional,
            deadline: Math.floor(Date.now() / 1000) + 3600,
        },
    });

    const txHash = await submitTx(calldata.toAddress, calldata.callData, calldata.value);

    await tradingApiRequest(config, "POST", "/api/v1/orders/tx", {
        body: { txHash },
    });

    return txHash;
}
```

## Stock deposit and withdrawal (self-submit)

Stock endpoints move ERC-20 stock tokens between wallet balance and `exchangeBalance`. Approve `StockRouter` for the stock token before calling stock deposit calldata.

```ts
async function depositStockSelfSubmit(stockTokenAddress: Address, tokenAmount: string): Promise<Hex> {
    const calldata = await tradingApiRequest<OrderCalldata>(config, "POST", "/api/v1/stock/deposits/calldata", {
        body: {
            userAddress: account.address,
            tokenAddress: stockTokenAddress,
            tokenAmount,
        },
    });

    const txHash = await submitTx(calldata.toAddress, calldata.callData, calldata.value);

    await tradingApiRequest(config, "POST", "/api/v1/stock/deposits/tx", {
        body: { txHash },
    });

    return txHash;
}

async function withdrawStockSelfSubmit(stockTokenAddress: Address, tokenAmount: string): Promise<Hex> {
    const calldata = await tradingApiRequest<OrderCalldata>(config, "POST", "/api/v1/stock/withdrawals/calldata", {
        body: {
            userAddress: account.address,
            tokenAddress: stockTokenAddress,
            tokenAmount,
        },
    });

    const txHash = await submitTx(calldata.toAddress, calldata.callData, calldata.value);

    await tradingApiRequest(config, "POST", "/api/v1/stock/withdrawals/tx", {
        body: { txHash },
    });

    return txHash;
}
```

## One Click enable (viem signTypedData)

User signs the EIP-712 payload from `/1ct/prepare`, then submits to `/1ct/enable`.

```ts
interface OneClickPrepare {
    delegatee: Address;
    signPayload: {
        domain: {
            name: string;
            version: string;
            chainId: number;
            verifyingContract: Address;
        };
        types: {
            // /1ct/prepare also returns EIP712Domain here; it must be stripped
            // before passing to viem signTypedData (viem derives it from `domain`).
            EIP712Domain?: Array<{ name: string; type: string }>;
            Delegate: Array<{ name: string; type: string }>;
        };
        primaryType: "Delegate";
        message: {
            user: Address;
            delegatee: Address;
            nonce: bigint | number;
            deadline: bigint | number;
        };
    };
    nonce: number;
    deadline: number;
}

async function enableOneClick(): Promise<void> {
    const deadline = Math.floor(Date.now() / 1000) + 3600;
    const prepared = await tradingApiRequest<OneClickPrepare>(config, "POST", "/api/v1/1ct/prepare", {
        body: { deadline },
    });

    const { signPayload } = prepared;
    if (account.address.toLowerCase() !== signPayload.message.user.toLowerCase()) {
        throw new Error("signer must match signPayload.message.user");
    }

    // viem derives EIP712Domain from `domain`; leaving it in `types` throws, so strip it.
    const types = { ...signPayload.types };
    delete types.EIP712Domain;
    const signature = await walletClient.signTypedData({
        domain: {
            ...signPayload.domain,
            chainId: BigInt(signPayload.domain.chainId),
        },
        types,
        primaryType: signPayload.primaryType,
        message: {
            ...signPayload.message,
            nonce: BigInt(signPayload.message.nonce),
            deadline: BigInt(signPayload.message.deadline),
        },
    });

    await tradingApiRequest(config, "POST", "/api/v1/1ct/enable", {
        body: { signature, deadline: prepared.deadline },
    });
}
```

After enable, use `/send` endpoints — see [one-click-flow.md](/rwa-trading-apis/guides/one-click-flow.md).

## One Click order (via `/send`)

Once One Click is enabled, place orders with `/orders/send`. The API builds the calldata and broadcasts it through `OneClickRouter`, paying gas on the user's behalf — no per-order wallet signature or self-broadcast. The body is the same as `/orders/calldata`, plus an optional `gasLimit` (set a buffer; router calls can under-estimate gas on some chains). The response is the order-tx mapping, not calldata: poll it (or `/orders/tx/{txHash}`) until `orderId` is populated.

```ts
interface OneClickOrderTx {
    id: string;
    chainId: number;
    productType: string;
    operationType: string;      // "trade"
    orderId: string | null;     // null until indexed, then the on-chain order id
    txHash: string;
    status: string;             // "pending" -> "new" -> ...
    createAt: string;
    updateAt: string;
}

async function placeLimitBuyOneClick(
    stockAddress: Address,
    quantity: string,
    price: string,
): Promise<OneClickOrderTx> {
    return tradingApiRequest<OneClickOrderTx>(config, "POST", "/api/v1/orders/send", {
        body: {
            stockAddress,
            side: "Buy",
            type: "Limit",
            quantity,
            price,
            timeInForce: "DAY", // production limit orders only accept DAY
            deadline: Math.floor(Date.now() / 1000) + 3600,
            gasLimit: 800000,   // gas buffer; API broadcasts via OneClickRouter
        },
    });
}
```

Cancel an open One Click order the same way — `DELETE /api/v1/orders/{orderId}/send` (query `deadline` required, `gasLimit` optional) — which also broadcasts through the router.

## Query portfolio

```ts
interface UserBalance {
    chainId: number;
    productType: string;
    address: Address;
    mUsdBalance: string;
    tokenBalances: Array<{
        symbol: string;
        stockSymbol?: string;
        isStock: boolean;
        walletBalance: string;
        exchangeBalance: string;
        price?: number;
    }>;
}

async function getPortfolio(userId: Address): Promise<UserBalance> {
    return tradingApiRequest<UserBalance>(
        config,
        "GET",
        `/api/v1/users/${userId.toLowerCase()}/balance`,
    );
}
```

## Query corporate actions

```ts
interface BrokerCorporateAction {
    symbol: string;
    type: "reverse_splits" | "forward_splits" | "unit_splits" | "stock_dividends" | "cash_dividends";
    time: string;
    exDate: string;
    recordDate: string;
    payableDate: string;
    processDate: string;
    info: Record<string, unknown>;
}

async function getCorporateActions(symbol: string): Promise<BrokerCorporateAction[]> {
    return tradingApiRequest<BrokerCorporateAction[]>(config, "GET", "/api/v1/corporate-action", {
        query: { symbol, types: ["cash_dividends"] },
    });
}
```

See also [authenticate/examples.md](/rwa-trading-apis/authentication/examples.md) for Node.js-only HMAC snippets.


---

# 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.monday.trade/rwa-trading-apis/guides/demo-code.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.
