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.
- 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.
- Validation: Dinari confirms the account's eligibility and balance, then prepares the order for execution. Orders that fail these checks never reach the market.
- 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.
- 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:
- Entity has a valid KYC
- Entity has a valid account
- Entity has enough funds
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
Accountswith a Dinari-managed wallet. Dinari handles custody and settlement. - Unmanaged Orders (self-custody) — for
Accountsusing their own wallet. Orders are placed via smart contract interactions on an EVM chain, using a permit-based flow.
Managed Orders
Each managed order SDK function wraps one of the following API endpoints:
| Order type | SDK function (TS shown) | API endpoint |
|---|---|---|
| Market Buy | client.v2.accounts.orderRequests.createMarketBuy() | Create Market Buy Managed Order Request — POST /api/v2/accounts/{account_id}/order_requests/market_buy |
| Market Sell | client.v2.accounts.orderRequests.createMarketSell() | Create Market Sell Managed Order Request — POST /api/v2/accounts/{account_id}/order_requests/market_sell |
| Limit Buy | client.v2.accounts.orderRequests.createLimitBuy() | Create Limit Buy Managed Order Request — POST /api/v2/accounts/{account_id}/order_requests/limit_buy |
| Limit Sell | client.v2.accounts.orderRequests.createLimitSell() | Create Limit Sell Managed Order Request — POST /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
EIP155OrderRequestID and the signed permit, prepares a transaction to be placed on EVM. The returned structure contains the necessary data to create anEIP155Transactionobject (used in the User Sponsored flow). - Submit EIP-155 Order Request — submits a transaction for an EIP-155 Order Request given the
EIP155OrderRequestID and permit signature; Dinari creates and submits the transaction on your behalf and returns theEIP155OrderRequestrepresenting 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:
- Create permit — call the API to generate a permit for the order.
- Sign permit — use your wallet's private key to sign the permit message.
- Create permit transaction — submit the signed permit to receive transaction details.
- 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:
- Create permit - generate an order permit via the API.
- Sign the permit - sign the permit using your wallet credentials.
- 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).signatureUpdated 17 days ago
