Placing Orders

Dinari allows users to place orders utilzing the Dinari API and via EVM Direct Interactions

Order Lifecycle

Every dShare order, whether it's a buy or sell, moves through the same four stages. Only the direction of value changes: a buy converts payment tokens into dShares, and a sell converts dShares back into payment tokens.

  1. Validation: An order is placed against an account that has completed KYC and holds sufficient funds in payment tokens for a buy, or sufficient dShares for a sell. Dinari returns an order record immediately so the order can be tracked from this point forward.
  2. Validation: Dinari confirms the account's eligibility and balance, then prepares the order for execution. Orders that fail these checks never reach the market.
  3. Execution: The order is routed to be filled against the underlying security. Market orders fill at the prevailing price; limit orders wait until the limit price is reached, and may expire unfilled.
  4. Settlement: Once filled, the result is settled on-chain in a single step: a buy mints dShares to the user's wallet, and a sell burns the dShares and releases the proceeds. Tokens and balances only ever move together with the fill a partially settled order is not possible.

An order is complete once it reaches a terminal state. Fulfilled means the fill and the on-chain settlement both succeeded. Cancelled or expired means nothing was minted or burned, and the committed funds are returned: the payment for a buy, or the dShares for a sell.

The diagrams below trace this flow end to end for each side of the trade.

Buying a dShare

sequenceDiagram
    autonumber
    participant U as End user
    participant P as Partner
    participant D as Dinari
    participant B as Dinari Securities
    participant C as Chain

    U->>P: Requests a buy<br/>(payment amount in stablecoin)
    P->>D: POST /accounts/{id}/order_requests/market_buy
    D-->>P: OrderRequest:<br/>order_request_id, status
    D->>D: Verifies KYC, account,<br/>and available funds

    D->>B: Routes order for execution
    B-->>D: Fill confirmation:<br/>filled_qty, filled_avg_price
    D->>C: Mints dShares<br/>to the user's wallet
    C-->>D: Fulfillment confirmed

    P->>D: GET /accounts/{id}/orders/{order_id}
    D-->>P: Order status: FULFILLED
    P->>U: Confirm dShares received

Selling a dShare

sequenceDiagram
    autonumber
    participant U as End user
    participant P as Partner
    participant D as Dinari
    participant B as Dinari Securities
    participant C as Chain

    U->>P: Requests a sell<br/>(quantity of dShares)
    P->>D: POST /accounts/{id}/order_requests/market_sell
    D-->>P: OrderRequest:<br/>order_request_id, status
    D->>D: Verifies KYC, account,<br/>and dShare balance

    D->>B: Routes order for execution
    B-->>D: Fill confirmation:<br/>filled_qty, filled_avg_price
    D->>C: Burns dShares and releases<br/>stablecoin proceeds to the user's wallet
    C-->>D: Fulfillment confirmed

    P->>D: GET /accounts/{id}/orders/{order_id}
    D-->>P: Order status: FULFILLED
    P->>U: Credit proceeds and notify
ℹ️

If an order cannot be completed then no tokens are minted or burned. A buy returns the payment to the user, and a sell returns the dShares.

Requirements for dShare Orders

Before an Entity can place a dShare order, make sure it fulfills the following pre-requisites:

How Orders are Placed

The Dinari SDKs wrap the Dinari REST API, mapping each SDK function directly to an API endpoint. They handle authentication, request construction, and response parsing, while SDK parameters and return objects match the underlying endpoint's request body and response.

The API Reference is the source of truth for both integration styles. Use the endpoint reference pages below to see input fields, validation rules, and response schemas.

There are two ways to place an order:

  • Managed Orders — for Accounts with a Dinari-managed wallet. Dinari handles custody and settlement.
  • Unmanaged Orders (self-custody) — for Accounts using their own wallet. Orders are placed via smart contract interactions on an EVM chain, using a permit-based flow.

Managed Orders

❗️

This is only usable by Accounts with a Dinari-managed wallet.

Each managed order SDK function wraps one of the following API endpoints:

Order typeSDK function (TS shown)API endpoint
Market Buyclient.v2.accounts.orderRequests.createMarketBuy()Create Market Buy Managed Order RequestPOST /api/v2/accounts/{account_id}/order_requests/market_buy
Market Sellclient.v2.accounts.orderRequests.createMarketSell()Create Market Sell Managed Order RequestPOST /api/v2/accounts/{account_id}/order_requests/market_sell
Limit Buyclient.v2.accounts.orderRequests.createLimitBuy()Create Limit Buy Managed Order RequestPOST /api/v2/accounts/{account_id}/order_requests/limit_buy
Limit Sellclient.v2.accounts.orderRequests.createLimitSell()Create Limit Sell Managed Order RequestPOST /api/v2/accounts/{account_id}/order_requests/limit_sell

Parameters

The parameters below apply to both the API and the SDKs — they are the same. In the API, account_id is a path parameter and the rest are body fields; in the SDKs, account_id is the first argument and the rest are passed as function parameters.

For the authoritative, per-endpoint list of input values and validation rules, see the corresponding API references: Market Buy, Market Sell, Limit Buy, Limit Sell.

Response

All four endpoints — and therefore all four SDK functions — return the same object: an OrderRequest. The response is identical whether you call the API directly or through an SDK.

The full response schemas, including error responses (422, 423), are documented on each endpoint's API reference page linked above.

Example (SDK)

import Dinari from '@dinari/api-sdk';

const client = new Dinari({
  apiKeyID: process.env['DINARI_API_KEY_ID'],
  apiSecretKey: process.env['DINARI_API_SECRET_KEY'],
  environment: 'sandbox',
});

async function main() {
  const accountID = 'your-account-id';
  const stockID = 'your-stock-id';

  const limitBuy = await client.v2.accounts.orderRequests.createLimitBuy(accountID, {
    asset_quantity: 1,
    limit_price: 1.5,
    stock_id: stockID,
  });
  console.log('Limit Buy Response:', limitBuy);

  const limitSell = await client.v2.accounts.orderRequests.createLimitSell(accountID, {
    asset_quantity: 1,
    limit_price: 1.5,
    stock_id: stockID,
  });
  console.log('Limit Sell Response:', limitSell);

  const marketBuy = await client.v2.accounts.orderRequests.createMarketBuy(accountID, {
    stock_id: stockID,
    payment_amount: 150.0,
  });
  console.log('Market Buy Response:', marketBuy);

  const marketSell = await client.v2.accounts.orderRequests.createMarketSell(accountID, {
    stock_id: stockID,
    asset_quantity: 1,
  });
  console.log('Market Sell Response:', marketSell);
}

main();
import os
from dinari_api_sdk import Dinari

client = Dinari(
    api_key_id=os.environ.get("DINARI_API_KEY_ID"),
    api_secret_key=os.environ.get("DINARI_API_SECRET_KEY"),
    environment="sandbox",
)

account_id = "your-account-id"
stock_id = "your-stock-id"

limit_buy = client.v2.accounts.order_requests.create_limit_buy(
    account_id=account_id,
    asset_quantity=1,
    limit_price=1.5,
    stock_id=stock_id,
)

limit_sell = client.v2.accounts.order_requests.create_limit_sell(
    account_id=account_id,
    asset_quantity=1,
    limit_price=1.5,
    stock_id=stock_id,
)

market_buy = client.v2.accounts.order_requests.create_market_buy(
    account_id=account_id,
    stock_id=stock_id,
    payment_amount=150.0,
)

market_sell = client.v2.accounts.order_requests.create_market_sell(
    account_id=account_id,
    stock_id=stock_id,
    asset_quantity=1,
)
package main

import (
	"context"
	"fmt"
	"log"

	dinari "github.com/dinaricrypto/dinari-api-sdk-go"
	"github.com/dinaricrypto/dinari-api-sdk-go/option"
)

func main() {
	client := dinari.NewClient(
		option.WithEnvironmentSandbox(),
	)

	accountID := "your-account-id"
	stockID := "your-stock-id"

	limitBuyResp, err := client.V2.Accounts.OrderRequests.NewLimitBuy(context.TODO(), accountID, dinari.V2AccountOrderRequestNewLimitBuyParams{
		CreateLimitOrderInput: dinari.CreateLimitOrderInputParam{
			AssetQuantity: 1,
			LimitPrice:    1.5,
			StockID:       stockID,
		},
	})
	if err != nil {
		log.Fatalf("Failed to create limit buy order: %v", err)
	}
	fmt.Printf("Limit Buy Response: %+v\n", limitBuyResp)

	limitSellResp, err := client.V2.Accounts.OrderRequests.NewLimitSell(context.TODO(), accountID, dinari.V2AccountOrderRequestNewLimitSellParams{
		CreateLimitOrderInput: dinari.CreateLimitOrderInputParam{
			AssetQuantity: 1,
			LimitPrice:    1.5,
			StockID:       stockID,
		},
	})
	if err != nil {
		log.Fatalf("Failed to create limit sell order: %v", err)
	}
	fmt.Printf("Limit Sell Response: %+v\n", limitSellResp)

	marketBuyResp, err := client.V2.Accounts.OrderRequests.NewMarketBuy(context.TODO(), accountID, dinari.V2AccountOrderRequestNewMarketBuyParams{
		PaymentAmount: 150.0,
		StockID:       stockID,
	})
	if err != nil {
		log.Fatalf("Failed to create market buy order: %v", err)
	}
	fmt.Printf("Market Buy Response: %+v\n", marketBuyResp)

	marketSellResp, err := client.V2.Accounts.OrderRequests.NewMarketSell(context.TODO(), accountID, dinari.V2AccountOrderRequestNewMarketSellParams{
		AssetQuantity: 1,
		StockID:       stockID,
	})
	if err != nil {
		log.Fatalf("Failed to create market sell order: %v", err)
	}
	fmt.Printf("Market Sell Response: %+v\n", marketSellResp)
}

Unmanaged Orders

For Accounts using their own wallet, orders are placed on-chain via a permit-based flow on EVM (EIP-155) chains. As with managed orders, the SDK functions for this flow are wrappers around API endpoints — the parameters and responses are the same in both. The two endpoints involved are:

  • Create EIP-155 Order Request Permit Transaction — given the EIP155OrderRequest ID and the signed permit, prepares a transaction to be placed on EVM. The returned structure contains the necessary data to create an EIP155Transaction object (used in the User Sponsored flow).
  • Submit EIP-155 Order Request — submits a transaction for an EIP-155 Order Request given the EIP155OrderRequest ID and permit signature; Dinari creates and submits the transaction on your behalf and returns the EIP155OrderRequest representing the proxied order (used in the Dinari Sponsored flow).

See each reference page for the exact input values and response schemas. Below the examples below on how to places these orders using the Dinari SDK.

EVM

User Sponsored Orders

You create and send the transaction yourself, keeping full control of the blockchain interaction:

  1. Create permit — call the API to generate a permit for the order.
  2. Sign permit — use your wallet's private key to sign the permit message.
  3. Create permit transaction — submit the signed permit to receive transaction details.
  4. Sign and send transaction — sign the resulting transaction and broadcast it to the blockchain.
import os
from dinari_api_sdk import Dinari
from eth_account import Account
from eth_account.messages import encode_typed_data

client = Dinari(
    api_key_id=os.environ.get("DINARI_API_KEY_ID"),  # This is the default and can be omitted
    api_secret_key=os.environ.get("DINARI_API_SECRET_KEY"),  # This is the default and can be omitted
    environment="sandbox", # defaults to "production"
)

account_id = "your-account-id"
# Step 1. Create permit
resp = client.v2.accounts.order_requests.eip155.create_permit(
    account_id,
    chain_id="eip155:11155111",
    order_side="BUY",
    order_tif="DAY",
    order_type="MARKET",
    payment_token="0xC6E13d97d73721ecA623F6d6c77ceBAFD2fF6431",
    payment_token_quantity=1.5,
    stock_id=stock_id,
)

# Step 2. Sign permit
private_key = "your-wallet-private-key"
acct = Account.from_key(private_key)
message = encode_typed_data(full_message=resp.permit)
signature = acct.sign_message(message).signature

# Step 3. Create permit transaction
tx_resp = client.v2.accounts.order_requests.eip155.create_permit_transaction(
    account_id,
    order_request_id=resp.order_request_id,
    permit_signature="0x" + signature.hex(),
)

# Step 4. Sign and send transaction
w3 = Web3(HTTPProvider("localhost:8545"))

tx = {
    "to": tx_resp.contract_address,
    "data": tx_resp.data,
    "value": tx_resp.value,
    "gas": w3.eth.estimate_gas(
        {
            "from": acct.address,
            "to": tx_resp.contract_address,
            "data": HexStr(tx_resp.data),
            "value": Wei(int(tx_resp.value)),
        }
    ),
    "gasPrice": w3.eth.gas_price,
    "nonce": w3.eth.get_transaction_count(acct.address),
    "chainId": 421614,  # e.g. Arbitrum Sepolia
}

signed = acct.sign_transaction(tx)
txHash = w3.eth.send_raw_transaction(signed.rawTransaction)
import { V2 } from "@dinari/api-sdk/resources/index";

import * as AccountsAPI from "@dinari/api-sdk/resources/v2/accounts";
import {
  WalletClient,
  Address,
  Account,
  createWalletClient,
  createPublicClient,
  http,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { arbitrumSepolia } from "viem/chains";
import { Eip155CreatePermitResponse } from "@dinari/api-sdk/resources/v2/accounts/order-requests";

export async function signPermitMessageForViem(
  walletClient: WalletClient,
  permit: NonNullable<Eip155CreatePermitResponse["permit"]>,
  account?: Account,
): Promise<`0x${string}`> {
  if (
    !permit?.domain ||
    !permit?.types ||
    !permit?.message ||
    !permit?.primaryType
  ) {
    throw new Error("permit is missing required fields");
  }

  let resolvedAccount: Address | Account;
  if (account) {
    resolvedAccount = account;
  } else {
    [resolvedAccount] = await walletClient.requestAddresses();
  }

  // The permit is already in the correct shape (domain, types, primaryType, message)
  // In Python: encode_typed_data(full_message=resp.permit) then sign_message(message)
  // But since we're using viem, we can just sign the typed data directly
  // which handles the encoding and signing in one step
  const signature = await walletClient.signTypedData({
    account: resolvedAccount,
    domain: permit.domain as any,
    types: permit.types as any,
    primaryType: (permit.primaryType ?? "") as string,
    message: permit.message as any,
  });

  return signature;
}

export async function userSponsored(client: V2, accountId: string) {
  const stocks = await client.marketData.stocks.list();
  const stockId = stocks[0].id;

  // Replace with your chain ID (e.g., "eip155:421614" for Arbitrum Sepolia)
  const caip2ChainId: AccountsAPI.Chain =
    (process.env.PROXIED_CHAIN_ID as AccountsAPI.Chain) ?? "eip155:1337";
  const permitResponse: Eip155CreatePermitResponse =
    await client.accounts.orderRequests.eip155.createPermit(accountId, {
      chain_id: caip2ChainId,
      order_side: "BUY",
      order_tif: "DAY",
      order_type: "MARKET",
      stock_id: stockId,
      // Replace with your payment token address
      payment_token: process.env.PAYMENT_TOKEN || "",
      payment_token_quantity: 1.5,
    });

  // Replace with your private key (without 0x prefix)
  const PRIVATE_KEY = process.env.PRIVATE_KEY;
  const account = privateKeyToAccount(`0x${PRIVATE_KEY}`);

  const rpcUrl = "https://sepolia-rollup.arbitrum.io/rpc";
  const walletClient = createWalletClient({
    account,
    chain: arbitrumSepolia,
    transport: http(rpcUrl),
  });

  const publicClient = createPublicClient({
    chain: arbitrumSepolia,
    transport: http(rpcUrl),
  });

  // Step 2. Sign the permit
  const permitSignature = await signPermitMessageForViem(
    walletClient,
    permitResponse.permit,
    account,
  );

  // Step 3. Create permit transaction
  const transactionResponse =
    await client.accounts.orderRequests.eip155.createPermitTransaction(
      accountId,
      {
        order_request_id: permitResponse.order_request_id,
        permit_signature: permitSignature,
      },
    );

	// Step 4. Send the transaction
}

async function main() {
  const client = new Dinari({
    apiKeyID: process.env["DINARI_API_KEY_ID"],
    apiSecretKey: process.env["DINARI_API_SECRET_KEY_ID"],
  });
  await userSponsored(client.v2, "your_account_id");
}
package main

import (
	"context"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"

	"github.com/chenzhijie/go-web3"
	dinari "github.com/dinaricrypto/dinari-api-sdk-go"
	"github.com/dinaricrypto/dinari-api-sdk-go/option"
	"github.com/dinaricrypto/dinari-api-sdk-go/packages/param"
	"github.com/ethereum/go-ethereum/signer/core/apitypes"
)

func main() {
	// DINARI_API_KEY_ID and DINARI_API_SECRET_KEY are set as environment variables
	client := dinari.NewClient(
		option.WithEnvironmentSandbox(), // Defaults to production when omitted
	)
	accountID := "your account id"

	ctx := context.TODO()

	prepareOrderBody := dinari.V2AccountOrderRequestEip155NewPermitParams{
		ChainID:              dinari.ChainEip155_421614,
		OrderSide:            dinari.OrderSideBuy,
		OrderTif:             dinari.OrderTifDay,
		OrderType:            dinari.OrderTypeMarket,
		StockID:              param.Opt[string]{Value: "0196d545-d8a8-7210-8cd1-a49e82c31e53"},
		PaymentToken:         "0x6a34FDFE60D1758dF5b577d413E37397D21c3E78",
		PaymentTokenQuantity: dinari.Float(1.5),
	}
	// Step 1. Create the order permit
	permitResp, err := client.V2.Accounts.OrderRequests.Eip155.NewPermit(ctx, accountID, prepareOrderBody)
	if err != nil {
		log.Fatalf("Failed: %v", err)
	}
	fmt.Printf("hashes: %+v\n", permitResp.Permit["types"])

	var rpcProviderURL = "https://sepolia-rollup.arbitrum.io/rpc"
	w3, err := web3.NewWeb3(rpcProviderURL)
	if err != nil {
		panic(err)
	}
	w3.Eth.SetChainId(421614)

	// Step 2. Sign the permit
	privateKeyHex := "your private key"
	err = w3.Eth.SetAccount(privateKeyHex)
	if err != nil {
		panic(err)
	}
	permit := permitResp.Permit
	var types apitypes.Types
	var domain apitypes.TypedDataDomain
	var message apitypes.TypedDataMessage

	typesJSON, err := json.Marshal(permit["types"])
	if err != nil {
		log.Fatalf("failed to marshal types: %v", err)
	}
	if err := json.Unmarshal(typesJSON, &types); err != nil {
		log.Fatalf("failed to unmarshal types: %v", err)
	}

	domainJSON, err := json.Marshal(permit["domain"])
	if err != nil {
		log.Fatalf("failed to marshal domain: %v", err)
	}
	if err := json.Unmarshal(domainJSON, &domain); err != nil {
		log.Fatalf("failed to unmarshal domain: %v", err)
	}

	messageJSON, err := json.Marshal(permit["message"])
	if err != nil {
		log.Fatalf("failed to marshal message: %v", err)
	}
	if err := json.Unmarshal(messageJSON, &message); err != nil {
		log.Fatalf("failed to unmarshal message: %v", err)
	}

	permitTypedData := apitypes.TypedData{
		Types:       types,
		PrimaryType: permit["primaryType"].(string),
		Domain:      domain,
		Message:     message,
	}
	signedPermit, err := w3.Eth.SignTypedData(permitTypedData)
	if err != nil {
		log.Fatalf("failed sign typed data: %v", err)
	}
	signedPermitHexString := fmt.Sprintf("0x%s", hex.EncodeToString(signedPermit))

	fmt.Printf("signedPermit: %s\n", signedPermitHexString)

	// Step 3. Create the transaction
	permitTransactionBody := dinari.V2AccountOrderRequestEip155NewPermitTransactionParams{
		OrderRequestID:  permitResp.OrderRequestID,
		PermitSignature: signedPermitHexString,
	}
	permitTransaction, err := client.V2.Accounts.OrderRequests.Eip155.NewPermitTransaction(ctx, accountID, permitTransactionBody)
	if err != nil {
		log.Fatalf("failed to create permit transaction: %v", err)
	}
	fmt.Printf("signedPermit: %+v\n", permitTransaction)

	// Step 4. Send the transaction
}

Dinari Sponsored

After signing the permit, you submit the signature to Dinari, which creates and submits the transaction on your behalf:

  1. Create permit - generate an order permit via the API.
  2. Sign the permit - sign the permit using your wallet credentials.
  3. Submit order through Dinari - send the signed permit to Dinari for processing rather than manually constructing a transaction.
import os
from dinari_api_sdk import Dinari
from eth_account import Account
from eth_account.messages import encode_typed_data

client = Dinari(
    api_key_id=os.environ.get("DINARI_API_KEY_ID"),  # This is the default and can be omitted
    api_secret_key=os.environ.get("DINARI_API_SECRET_KEY"),  # This is the default and can be omitted
    environment="sandbox", # defaults to "production"
)

account_id = "your-account-id"
# Step 1. Create permit
resp = client.v2.accounts.order_requests.eip155.create_permit(
    account_id,
    chain_id="eip155:11155111",
    order_side="BUY",
    order_tif="DAY",
    order_type="MARKET",
    payment_token="0xC6E13d97d73721ecA623F6d6c77ceBAFD2fF6431",
    payment_token_quantity=1.5,
    stock_id=stock_id,
)

# Step 2. Sign permit
private_key = "your-wallet-private-key"
acct = Account.from_key(private_key)
message = encode_typed_data(full_message=resp.permit)
signature = acct.sign_message(message).signature

Did this page help you?