Placing Orders
Dinari allows users to place orders utilzing the Dinari API and via EVM Direct Interactions
---
title: Lifecycle of a dShare Order
---
flowchart TD
START["Account ready<br/>KYC approved + funds available<br/>BUY: payment token (e.g. USDC) · SELL: dShares"]
START --> WALLET{"How is your<br/>wallet managed?"}
WALLET -->|"Dinari-managed wallet"| MANAGED["Place order via API<br/>market or limit · buy or sell"]
WALLET -->|"Self-custody wallet"| PERMIT["Create + sign an order permit,<br/>then submit it on-chain<br/>(yourself, or via Dinari)"]
MANAGED --> PROCESSING["Order processing<br/>Dinari validates the order and<br/>routes it for execution"]
PERMIT --> PROCESSING
PROCESSING --> EXECUTED["Order executed<br/>at market or limit price"]
EXECUTED --> SIDE{"Buy or sell?"}
SIDE -->|"BUY"| MINT["dShare tokens are minted<br/>to your wallet"]
SIDE -->|"SELL"| BURN["dShare tokens are burned;<br/>proceeds (USDC) are sent<br/>to your wallet"]
MINT --> DONE["Order fulfilled ✅"]
BURN --> DONE
PROCESSING -.->|"Order can't be completed<br/>(e.g. cancelled or expired)"| REFUND["Order cancelled<br/>BUY: payment refunded<br/>SELL: dShares returned"]
classDef good fill:#d3f2e4,stroke:#1f8a5b,color:#0b3d27
classDef bad fill:#fde2e0,stroke:#c2453c,color:#5a1410
classDef step fill:#e3ecfb,stroke:#3568c4,color:#132b52
class DONE,MINT,BURN good
class REFUND bad
class MANAGED,PERMIT,PROCESSING,EXECUTED step
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 the SDKs relate to the Dinari API
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 (Self-Custody)
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 about 1 hour ago
