Placing dShare Orders with Alchemy
Ordering with Alchemy Account Kit Smart Wallet
This guide shows how to place a dShare order using the Dinari Partner SDK (@dinari/api-sdk) where an Alchemy Account Kit smart contract wallet signs the order intent.
How it works
When the funder is an Account Kit smart wallet, you must use the externally-signed path. The flow is:
- Create an order request and get back the EIP-712 permit to sign.
- Sign the permit with the smart wallet (produces an ERC-1271 contract-account signature).
- Submit the signed permit — Dinari-sponsored or self-sponsored.
- Poll the order request until it reaches a terminal state.
NOTE: Smart-wallet signatures are not EOA signaturesA standard ERC-2612
permit()recovers the signer withecrecover, which only accepts EOA signatures and will reject a smart-contract wallet. A contract-wallet (ERC-1271) signature is only honored if the verifying contract supports ERC-1271 as Uniswap Permit2 does. Confirm your payment path validates ERC-1271/Permit2 (not raw ERC-2612) before going to mainnet.
Prerequisites
To place an order, an Entity needs:
- A valid KYC on the Entity.
- A valid Account under the Entity.
- Enough payment-token balance in the smart wallet to cover the order plus fees.
For this integration you also need:
- A Dinari partner account with an API Key ID and API Secret Key (
partners.dinari.com). Start in sandbox. - An Alchemy API key (
dashboard.alchemy.com), and optionally a Gas Manager policy ID for gas sponsorship. - The smart wallet's owner private key, held server-side in a secret store as a best practice. This is the signer/owner of the account — not the account address.
Step 1 — Initialize the Dinari client
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", // defaults to "production"
});Step 2 — Create the headless smart wallet client
This is the server-side signer client (not the React hooks path). The smart account is a Modular Account V2 whose owner is a LocalAccountSigner built from a private key.
import { createModularAccountV2Client } from "@account-kit/smart-contracts";
import { LocalAccountSigner } from "@aa-sdk/core";
import { alchemy, arbitrumSepolia } from "@account-kit/infra";
const owner = LocalAccountSigner.privateKeyToAccountSigner(
process.env.SMART_WALLET_OWNER_PRIVATE_KEY as `0x${string}`,
);
const smartAccountClient = await createModularAccountV2Client({
mode: "default",
chain: arbitrumSepolia,
transport: alchemy({ apiKey: process.env.ALCHEMY_API_KEY! }),
signer: owner,
});
// The smart contract account address (not the owner EOA):
const smartWalletAddress = smartAccountClient.account.address;signTypedData on this client produces a signature validatable via ERC-1271. If the account is not yet deployed, the signature is wrapped per ERC-6492.
Step 3 — Deploy the smart account
A Modular Account V2 deploys on its first user operation. On-chain ERC-1271 validation requires the account to be deployed (unless the verifier supports ERC-6492), so deploy it before signing permits.
const isDeployed = await smartAccountClient.account.isAccountDeployed();
if (!isDeployed) {
const deployOp = await smartAccountClient.sendUserOperation({
uo: { target: smartAccountClient.account.address, data: "0x", value: 0n },
});
await smartAccountClient.waitForUserOperationTransaction({ hash: deployOp.hash });
}
Deployment needs gasThe deploy user operation costs gas. Fund the account with native gas or attach an Alchemy Gas Manager policy to the client.
Step 4 — Complete KYC and link your wallet (one-time)
These steps run once per user. KYC requires the user to complete a hosted flow, so it cannot be fully automated server-side.
// Entity + KYC
const entity = await dinari.v2.entities.create({ name: "Jane Doe" });
const kyc = await dinari.v2.entities.kyc.createManagedCheck(entity.id);
console.log("Send the user to:", kyc.embed_url);
// Account
const account = await dinari.v2.entities.accounts.create(entity.id);
const accountId = account.id;
// Link the smart wallet (signed with the wallet, ERC-1271)
const nonceResp = await dinari.v2.accounts.wallet.external.getNonce(accountId, {
wallet_address: smartWalletAddress,
});
const linkSignature = await smartAccountClient.signMessage({
message: nonceResp.message,
});
await dinari.v2.accounts.wallet.external.connect(accountId, {
chain_id: "eip155:421614",
nonce: nonceResp.nonce,
signature: linkSignature,
wallet_address: smartWalletAddress,
});
Linking also uses an ERC-1271 signatureThe wallet-linking nonce is signed by the smart wallet, so the account must be deployed (Step 3) for the signature to validate.
Step 5 — Select the stock and order parameters
const stocks = await dinari.v2.marketData.stocks.list();
const stock = stocks.find((s) => s.symbol === "AAPL");
if (!stock) throw new Error("Stock not found");
const stockId = stock.id;| Field | Meaning |
|---|---|
stock_id | Dinari UUID of the dShare (from marketData.stocks.list()). |
order_side | "BUY" or "SELL". |
order_type | "MARKET" or "LIMIT". |
order_tif | Time-in-force, e.g. "DAY". |
payment_token | Payment-token ERC-20 address (e.g. USDC on the target chain). |
payment_token_quantity | For a market buy, the notional amount of payment token to spend. |
chain_id | CAIP-2 chain id, e.g. "eip155:421614". |
Amount fields by order typeMarket buys use
payment_token_quantity(notional). Market sells and limit orders useasset_quantity(number of dShares); limit orders also takelimit_price. Fees are added on top of the notional.
Step 6 — Create the order permit
const permitResponse = await dinari.v2.accounts.orderRequests.eip155.createPermit(
accountId,
{
chain_id: "eip155:421614",
order_side: "BUY",
order_type: "MARKET",
order_tif: "DAY",
stock_id: stockId,
payment_token: process.env.PAYMENT_TOKEN_ADDRESS!,
payment_token_quantity: 10.0, // notional to spend
},
);
const orderRequestId = permitResponse.order_request_id;
const permit = permitResponse.permit; // { domain, types, primaryType, message }The response carries an order_request_id and a permit object shaped exactly like an EIP-712 TypedDataDefinition.
Step 7 — Sign the permit with the smart wallet
const permitSignature = await smartAccountClient.signTypedData({
typedData: {
domain: permit.domain,
types: permit.types,
primaryType: permit.primaryType,
message: permit.message,
},
});Step 8 — Submit the signed order
Choose one of two submission modes.
Dinari-sponsored — Dinari pays gas and submits on-chain:
const result = await dinari.v2.accounts.orderRequests.eip155.submit(accountId, {
order_request_id: orderRequestId,
permit_signature: permitSignature,
});Self-sponsored — you send the transaction from the smart wallet:
const tx = await dinari.v2.accounts.orderRequests.eip155.createPermitTransaction(
accountId,
{ order_request_id: orderRequestId, permit_signature: permitSignature },
);
const op = await smartAccountClient.sendUserOperation({
uo: {
target: tx.contract_address as `0x${string}`,
data: tx.data as `0x${string}`,
value: BigInt(tx.value ?? 0),
},
});
const txHash = await smartAccountClient.waitForUserOperationTransaction({
hash: op.hash,
});Step 9 — Poll order status
const orderRequest = await dinari.v2.accounts.orderRequests.retrieve(
orderRequestId,
{ account_id: accountId },
);
console.log("Status:", orderRequest.status);Two status concepts exist:
OrderRequestStatus— the request status (pre-brokerage), on theOrderRequest.BrokerageOrderStatus— the resulting on-chain/brokerageOrder, viadinari.v2.accounts.orders.retrieve(orderId, { account_id }).
Poll the order request to a terminal state, then track the resulting order and fulfillments.
Common errors
| Symptom | Likely cause | Fix |
|---|---|---|
| Permit signature rejected on submit | Payment path uses raw ERC-2612 (ecrecover), which rejects contract-wallet signatures | Confirm Dinari's verifier supports ERC-1271 / Permit2 for your token and chain |
isValidSignature reverts | Smart account not deployed | Deploy the account first (Step 3) |
| Wallet-linking fails | Account not deployed when linking signature was produced | Deploy, then link |
| Insufficient funds | Payment-token balance below notional + fees | Fund the smart wallet; budget for fees on top of the notional |
| User operation fails in self-sponsored mode | No native gas and no paymaster | Fund the wallet or attach a Gas Manager policy |
