> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getpara.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tokenized Stocks via Dinari

> Let users buy and sell tokenized U.S. stocks (dShares) from a Para embedded wallet using Dinari's Enterprise API.

Combine Para's wallet infrastructure with [Dinari](https://dinari.com) to let users trade tokenized stocks (dShares) from a Para-powered wallet. Users pay in stablecoins and receive fully backed stock tokens directly in their wallet.

## What You'll Learn

* Create a Para account and wallet client
* Onboard users into Dinari with their Para wallet
* Order tokenized stocks with Dinari

Orders can be **gasless** (Dinari broadcasts the transaction) or **self-submitted** (the Para wallet sends it and pays gas).

<Warning>
  Dinari API keys are secret. **`server.ts`** blocks run on your backend; **`client.ts`** blocks run in the browser with Para. Pass addresses and signatures between them through your own API.
</Warning>

## Overview

The user signs in with Para, and your server creates a Dinari `Entity` and has the user complete Dinari's hosted KYC. Once KYC passes, you open a Dinari `Account` and link the user's Para wallet to it by having Para sign an ownership message. To trade, Dinari issues an order permit, Para signs it, and the order is submitted; the dShares then arrive in the user's Para wallet.

```mermaid theme={null}
graph LR
  A["1. Sign in<br/>with Para"] --> B["2. Create Entity"]
  B --> C["3. Verify identity<br/>(KYC)"]
  C --> D["4. Open Account"]
  D --> E["5. Link Para<br/>wallet"]
  E --> F["6. Sign order<br/>permit"]
  F --> G["7. Submit +<br/>track order"]
```

## Why Combine Para and Dinari

| Para Wallets | Dinari |
| - | - |
| Instant onboarding, MPC-secure, white-label | Tokenized U.S. stocks and ETFs (dShares), 1:1 backed |
| Embedded + external wallet support | Multi-chain support (Arbitrum, Base, Ethereum, and more) |
| Email/social login — no seed phrase or browser extension | Gasless order flow via signed permits |

## What You Need

* **Para API key** from the Para dashboard
* **Dinari sandbox credentials** (`DINARI_API_KEY_ID`, `DINARI_API_SECRET_KEY`) from the [Dinari Partner Portal](https://partners.dinari.com). See the [Dinari Quickstart](https://docs.dinari.com/docs/quickstart) for setup.

## Part 1: Set Up Para and Dinari

### Step 1: Install and Configure

```bash theme={null}
# Install the Para SDK
npm install @getpara/viem-v2-integration @getpara/react-sdk

# Install the Dinari SDK
npm install @dinari/api-sdk viem
```

```bash .env theme={null}
# The Dinari API Key & Secret can be retrieved from https://partners.dinari.com/. A free sandbox is offered for development purposes only.
DINARI_API_KEY_ID=
DINARI_API_SECRET_KEY=
DINARI_ENV="sandbox"
# Supported Chains for Dinari: https://docs.dinari.com/docs/blockchain
DINARI_CHAIN_ID=
PAYMENT_TOKEN_ADDRESS="0xC6E13d97d73721ecA623F6d6c77ceBAFD2fF6431"

# API Key for Para can be found at https://developer.getpara.com/ in the Para Developer Portal.
VITE_PARA_API_KEY=
# RPC for the Chain being Used
VITE_RPC_URL=
```

### Step 2: Initialize the Clients

Complete Para authentication before signing anything.

```typescript client.ts theme={null}
import { createParaViemAccount, createParaViemClient } from "@getpara/viem-v2-integration";
import { ParaWeb } from "@getpara/react-sdk";
import { http, createPublicClient } from "viem";
import { sepolia } from "viem/chains"; // use `arbitrum` in production

const para = new ParaWeb("YOUR_PARA_API_KEY");
const account = createParaViemAccount({ para });

const walletClient = createParaViemClient({
  para,
  walletClientConfig: { account, chain: sepolia, transport: http("YOUR_RPC_URL") },
});
const publicClient = createPublicClient({ chain: sepolia, transport: http("YOUR_RPC_URL") });
```

Initialize the Dinari SDK with the API Key, Secret, and Environment. For additional supported languages, check out our documentation at [Dinari Quickstart](https://docs.dinari.com/docs/quickstart#3-install-an-sdk)

```typescript server.ts theme={null}
import Dinari from "@dinari/api-sdk";

const dinari = new Dinari({
  apiKeyID: process.env.DINARI_API_KEY_ID,
  apiSecretKey: process.env.DINARI_API_SECRET_KEY,
  environment: "sandbox"
});
```

## Part 2: Onboard the User

Each user maps to a Dinari `Entity` (holds KYC), which owns an `Account` (holds orders and balances). Every step below reuses what already exists, so retries and server restarts are safe.

### Step 3: Create an Entity

```typescript server.ts theme={null}
async function getOrCreateEntity(userId: string, name: string) {
  const { data: [existing] } = await dinari.v2.entities.list({ reference_id: userId });
  return existing ?? (await dinari.v2.entities.create({ name, reference_id: userId }));
}
```

Use your own stable user ID as `reference_id`, and persist the returned IDs in your database.

### Step 4: Verify Identity (KYC)

Dinari hosts the identity check. Create a managed check and show the returned URL to the user.

```typescript server.ts theme={null}
const { embed_url } = await dinari.v2.entities.kyc.createManagedCheck(entity.id, {
  jurisdiction: "BASELINE", // the body is required; use "US" for U.S. customers
});

// An iframe is returned for Identity Verification using Managed KYC
<iframe title="Identity verification" src={embedUrl} allow="camera; microphone" style={{ width: "100%", height: 640 }} />
<a href={embedUrl} target="_blank" rel="noopener noreferrer">Open in a new tab</a>
```

The hosted flow does not call back into your app, so poll the status every few seconds until it is `PASS`. Keep polling on `PENDING`, `INCOMPLETE`, or `NEEDS_REVIEW`; stop on `FAIL`.

```typescript server.ts theme={null}
async function getKycStatus(entityId: string) {
  // Wait for the KYC Status to Update. Once the user completes the embedded KYC Flow, the status will update to reflect as such.
  const kyc = await dinari.v2.entities.kyc.retrieve(entityId).catch(() => null);
  return kyc?.status ?? "NOT_STARTED";
}
```

### Step 5: Create an Account

Only after KYC passes.

```typescript server.ts theme={null}
async function getOrCreateAccount(entityId: string) {
  if ((await getKycStatus(entityId)) !== "PASS") throw new Error("KYC not approved");

  const { data: [existing] } = await dinari.v2.entities.accounts.list(entityId);
  // BASELINE is for all Ex-US Users. If you are offering to US Citizens then the jurisdiction should be US
  return existing ?? (await dinari.v2.entities.accounts.create(entityId, { jurisdiction: "BASELINE" }));
}
```

### Step 6: Link the Para Wallet

Dinari verifies wallet ownership with a signed message. `eip155:0` links the wallet as an EOA across all supported EVM chains.

```typescript server.ts theme={null}
import { getAddress } from "viem";

const paraAddress = getAddress(addressFromClient);

// An account can only link one wallet; skip if this one is already linked.
const linked = await dinari.v2.accounts.wallet.get(dinariAccount.id).catch(() => null);
if (linked?.address.toLowerCase() === paraAddress.toLowerCase()) return;

// 1. Get a single-use message to sign
const { nonce, message } = await dinari.v2.accounts.wallet.external.getNonce(dinariAccount.id, {
  wallet_address: paraAddress,
  chain_id: "eip155:0",
});

// 2. Sign from Para Wallet
const signature = await walletClient.signMessage({ account, message });

// 3. Verify and link
await dinari.v2.accounts.wallet.external.connect(dinariAccount.id, {
  wallet_address: paraAddress,
  chain_id: "eip155:0",
  nonce,
  signature,
});
```

## Part 3: Place an Order

### Step 7: Create an Order Permit

Find a dShare for the stock on your chain, then ask Dinari for an EIP-712 permit authorizing the payment.

```typescript server.ts theme={null}
const CHAIN_ID = process.env.DINARI_CHAIN_ID!;

const { data: stocks } = await dinari.v2.marketData.stocks.list({ symbols: ["AAPL"] });
const stock = stocks.find((s) => s.is_tradable && s.tokens.some((t) => t.startsWith(`${CHAIN_ID}:`)));
if (!stock) throw new Error(`AAPL has no dShare on ${CHAIN_ID}`);

const { order_request_id, permit } = await dinari.v2.accounts.orderRequests.eip155.createPermit(
  dinariAccount.id,
  {
    chain_id: CHAIN_ID,
    stock_id: stock.id,
    order_side: "BUY",
    order_type: "MARKET",
    order_tif: "DAY",
    payment_token: process.env.PAYMENT_TOKEN_ADDRESS!,
    payment_token_quantity: 100, // $100 as a decimal, not base units
  },
);
```

Market sells use `asset_token_quantity` instead; limit orders add `limit_price`.

### Step 8: Sign and Submit

```typescript client.ts theme={null}
const { EIP712Domain, ...types } = permit.types;

const permitSignature = await walletClient.signTypedData({
  account,
  domain: permit.domain,
  types,
  primaryType: permit.primaryType,
  message: permit.message,
});
```

<Tabs>
  <Tab title="Gasless (Dinari Submits)">
    Dinari broadcasts the order and pays the gas.

    ```typescript server.ts theme={null}
    await dinari.v2.accounts.orderRequests.eip155.submit(dinariAccount.id, {
      order_request_id,
      permit_signature: permitSignature,
    });
    ```
  </Tab>

  <Tab title="Self-Submitted">
    The wallet pays gas, so it needs the chain's native token.

    ```typescript server.ts theme={null}
    const tx = await dinari.v2.accounts.orderRequests.eip155.createPermitTransaction(dinariAccount.id, {
      order_request_id,
      permit_signature: permitSignature,
    });
    // Return { contract_address, data, value } to the client
    ```

    ```typescript client.ts theme={null}
    const hash = await walletClient.sendTransaction({
      account,
      chain: walletClient.chain,
      to: tx.contract_address,
      data: tx.data,
      value: BigInt(tx.value),
    });
    await publicClient.waitForTransactionReceipt({ hash });
    ```
  </Tab>
</Tabs>

### Step 9: Track the Order

```typescript server.ts theme={null}
const request = await dinari.v2.accounts.orderRequests.retrieve(order_request_id, {
  account_id: dinariAccount.id,
});
// Learn about the Market Behavior to expect at https://docs.dinari.com/docs/placing-orders
```

## Complete Example

The happy path from start to finish: a new user passes KYC, links their Para wallet, and buys \$100 of AAPL with a gasless order. Lines marked `// client` run in the browser with Para; everything else runs on your server.

```typescript theme={null}
import Dinari from "@dinari/api-sdk";
import { createParaViemAccount, createParaViemClient } from "@getpara/viem-v2-integration";
import { ParaWeb } from "@getpara/react-sdk";
import { getAddress, http } from "viem";
import { sepolia } from "viem/chains";

const CHAIN_ID = process.env.DINARI_CHAIN_ID!;

// Step 2: Initialize the clients
const para = new ParaWeb("YOUR_PARA_API_KEY"); // client: authenticate the user before signing
const account = createParaViemAccount({ para }); // client
const walletClient = createParaViemClient({ // client
  para,
  walletClientConfig: { account, chain: sepolia, transport: http("YOUR_RPC_URL") },
});

const dinari = new Dinari({
  apiKeyID: process.env.DINARI_API_KEY_ID,
  apiSecretKey: process.env.DINARI_API_SECRET_KEY,
  environment: "sandbox",
});

// Step 3: Create an entity
const entity = await dinari.v2.entities.create({ name: "Jane Doe", reference_id: "your-user-id" });

// Step 4: Verify identity. Show embed_url to the user, then poll until the status is PASS.
const { embed_url } = await dinari.v2.entities.kyc.createManagedCheck(entity.id, { jurisdiction: "BASELINE" });
while ((await dinari.v2.entities.kyc.retrieve(entity.id).catch(() => null))?.status !== "PASS") {
  await new Promise((r) => setTimeout(r, 4000));
}

// Step 5: Create an account
const dinariAccount = await dinari.v2.entities.accounts.create(entity.id, { jurisdiction: "BASELINE" });

// Step 6: Link the Para wallet
const paraAddress = getAddress(account.address);
const { nonce, message } = await dinari.v2.accounts.wallet.external.getNonce(dinariAccount.id, {
  wallet_address: paraAddress,
  chain_id: "eip155:0",
});
const signature = await walletClient.signMessage({ account, message }); // client
await dinari.v2.accounts.wallet.external.connect(dinariAccount.id, {
  wallet_address: paraAddress,
  chain_id: "eip155:0",
  nonce,
  signature,
});

// Step 7: Create an order permit
const { data: stocks } = await dinari.v2.marketData.stocks.list({ symbols: ["AAPL"] });
const stock = stocks.find((s) => s.tokens.some((t) => t.startsWith(`${CHAIN_ID}:`)))!;

const { order_request_id, permit } = await dinari.v2.accounts.orderRequests.eip155.createPermit(dinariAccount.id, {
  chain_id: CHAIN_ID,
  stock_id: stock.id,
  order_side: "BUY",
  order_type: "MARKET",
  order_tif: "DAY",
  payment_token: process.env.PAYMENT_TOKEN_ADDRESS!,
  payment_token_quantity: 100,
});

// Step 8: Sign and submit (gasless)
const { EIP712Domain, ...types } = permit.types;
const permitSignature = await walletClient.signTypedData({ // client
  account,
  domain: permit.domain,
  types,
  primaryType: permit.primaryType,
  message: permit.message,
});
await dinari.v2.accounts.orderRequests.eip155.submit(dinariAccount.id, {
  order_request_id,
  permit_signature: permitSignature,
});

// Step 9: Track the order
const request = await dinari.v2.accounts.orderRequests.retrieve(order_request_id, {
  account_id: dinariAccount.id,
});
console.log(request.status);
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.