> ## Documentation Index
> Fetch the complete documentation index at: https://sdk-guide.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Orders

> Create, sign, and execute multisig vault transactions with the Trustodian SDK

An order is a proposed transaction on a [multisig vault](https://sdk-guide.mintlify.site/sdk-guide/multisig.md). It carries the
payload, collects owner signatures, and becomes executable once the vault threshold is met.

The SDK exposes one set of methods across every chain, but the underlying work differs a lot: on EVM
signing is an off-chain EIP-712 signature; on Solana every step is its own on-chain transaction; on
Tron and XRP the SDK must fetch a server-built transaction before it can sign it; on Cardano it
produces a witness.

> [!NOTE]
>
> A Vault owner on a hardware wallet signs, creates, executes and rejects orders with the
> `prepareHardware…` methods instead. See the [method map](https://sdk-guide.mintlify.site/sdk-guide/hardware-wallets.md#method-map).

## Prerequisites

> [!NOTE]
>
> If the account runs in [strict authentication](https://sdk-guide.mintlify.site/sdk-guide/strict-authentication.md), pass
> `walletPassword` on every signing call. Without it the SDK throws
> `StrictAuthPasswordRequiredError`.

Every order method needs the same unlocked wallet session as personal transactions. See
[Authentication](https://sdk-guide.mintlify.site/sdk-guide/authentication.md#the-wallet-password-session) for session unlocking,
TTL, and lockout.

```typescript theme={null}
import { TrustodianSDK, EChainType } from "vaults-multichain-sdk";

const sdk = new TrustodianSDK({
  apiKey: process.env.TRUSTODIAN_API_KEY!,
  network: "mainnet",
  shareStore: myShareStore,
});

const userRef = await sdk.getSdkUserId();
await sdk.unlockWalletSession(userRef, walletPassword);
```

Every method that rebuilds a key (`createOrder` on SOL, `signOrder`, `rejectOrder` on SOL and
`executeOrder`) accepts `userRef` and, when the device share is missing,
`walletPassword`. `sdk.runWithUserRef(userRef, fn)` sets the ambient user for a whole block if you
would rather not thread it through every call.

## Method reference

| Method | Signature |
| - | - |
| `createOrder` | `(input: CreateOrderInput) => Promise<CreateOrderOutput>` |
| `createSendOrder` | `(input: CreateSendOrderInput) => Promise<CreateOrderOutput>` |
| `createSettingsOrder` | `(input: CreateSettingsOrderInput) => Promise<CreateOrderOutput>` |
| `signOrder` | `(input: SignOrderInput) => Promise<SignOrderOutput>` |
| `rejectOrder` | `(input: RejectOrderInput) => Promise<Order>` |
| `executeOrder` | `(input: ExecuteOrderInput) => Promise<ExecuteOrderOutput>` |
| `deleteOrder` | `(input: DeleteOrderInput) => Promise<DeleteResponse>` |
| `getOrders` | `(input: GetOrdersInput) => Promise<PaginatedOrdersResponse>` |
| `getOrderById` | `(input: GetOrderByIdInput) => Promise<{ order: Order }>` |
| `getMultisigTxHistory` | `(input: GetMultisigTxHistoryInput) => Promise<TxHistoryCombinedResponse>` |
| `getAllMultisigsTxHistory` | `(input?: GetAllMultisigsTxHistoryInput) => Promise<TxHistoryCombinedResponse>` |

> [!WARNING]
>
> Every method above that touches an order, from `createOrder` through `executeOrder`, first checks
> that the acting wallet still belongs to the account's current recovery phrase. A wallet whose
> `accessState` is `archived` or `access_lost` is refused with `PermissionDeniedError` before any
> order work happens. Sending the wallet's own funds is deliberately not gated this way. See
> [Wallet generations](https://sdk-guide.mintlify.site/sdk-guide/wallets.md#wallet-generations).

> [!NOTE]
>
> To price an execution before you commit to it, use `estimateFee` with the `ExecOrder` type:
>
> ```typescript theme={null}
> import { TransactionEstimatingType } from "vaults-multichain-sdk";
>
> const fee = await sdk.estimateFee({
>   chain: EChainType.EVM,
>   txType: TransactionEstimatingType.ExecOrder,
>   payload: { from: executorAddress, order, multisig },
>   chainId: multisig.chainId,
> });
> ```
>
> See [Multisig Network Fees](https://sdk-guide.mintlify.site/sdk-guide/multisig-fees.md).

## Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> proposed: createOrder (record only)
    proposed --> created: order materialised
    created --> pending: first signature
    pending --> pending: further signatures
    pending --> ready: threshold met
    ready --> execution: executeOrder broadcast
    execution --> completed: confirmed on chain
    pending --> rejected: rejection threshold met
    pending --> expired: TTL passed (TRON / XRP / ADA)
    ready --> expired: TTL passed (TRON / XRP / ADA)

    completed --> [*]
    rejected --> [*]
    expired --> [*]
```

`Order.status` is typed `EOrderStatus`, whose members are exactly:

| Member | Wire value | Meaning |
| - | - | - |
| `proposed` | `"proposed"` | Recorded in the database, not yet on chain |
| `created` | `"created"` | Order materialised, awaiting signatures |
| `pending` | `"pending"` | At least one signature, threshold not met |
| `ready` | `"ready"` | Signatures ≥ threshold, executable |
| `rejected` | `"rejected"` | Rejections make the threshold unreachable |
| `execution` | `"execution"` | Broadcast, awaiting chain confirmation |
| `expired` | `"expired"` | TTL passed before execution |
| `completed` | `"completed"` | Confirmed on chain |

## Order shape

```typescript theme={null}
interface Order {
  id: string;
  type?: string;
  multisigId: string;
  to: string;
  data: string;
  value: string;
  method: string;
  operation: number;
  hashToSing: string; // spelling is intentional: it is the wire field name
  txHash: string | null;
  updatedAt: string;
  createdAt: string;
  nonce: number;
  walletInitiator: string;
  signature: Signature;
  status: EOrderStatus;
  multisig?: MultisigsItem; // omitted by the paginated list; see Signing
  signatures: Signatures[]; // { walletAddress, signedHash, status }
  params?: OrderParams; // chain-specific, discriminated by chainType
  isSmallestNonce?: boolean;
}

interface OrderSigner {
  address: string;
  signature?: string;
  signedAt?: string;
}

interface PaginatedOrdersResponse {
  orders: Order[];
  total: number;
  page: number;
  limit: number;
}
```

## Instruction types

`createOrder` takes a `method: OrderMethodType` plus `instructions: OrderInstruction[]`.
`OrderInstruction` is a loose bag; the SDK reads only the fields the chosen method needs:

```typescript theme={null}
interface OrderInstruction {
  to?: string;
  symbol?: string; // on ADA, the asset name (e.g. "USDC")
  toPubkey?: string;
  amount?: string;
  tokenAddress?: string;
  decimals?: number;
  type?: string;
  newOwner?: string;
  owners?: string[];
  removeOwner?: string;
  ownerToRemove?: string;
  newThreshold?: number;
  data?: string;
  value?: string;
  memo?: string;
}
```

`OrderMethodType` covers every method any chain accepts: transfers (`native`, `erc20`, `spl`,
`trc20`, `ada_native`, `ada_token`, `xrp_native`, `xrp_asset`), owner and threshold changes, and the
XRP trust-line operations. Use the constant rather than a string literal, since some methods exist in
more than one casing for historical reasons.

### Cardano

```typescript theme={null}
enum EAdaInstructionType {
  native = "native",
  token = "token",
}

interface AdaNativeInstruction {
  type: EAdaInstructionType.native;
  to: string;
  amount: string;
}

interface AdaTokenInstruction {
  type: EAdaInstructionType.token;
  to: string;
  policyId: string;
  assetName: string;
  amount: string;
}

type TAdaCommandInstruction = AdaNativeInstruction | AdaTokenInstruction;
```

> [!NOTE]
>
> A Cardano order carries a single instruction, not an array. For a token transfer, pass the
> policy id as `tokenAddress` and the asset name as `symbol`, which is mandatory there.

### XRP

```typescript theme={null}
enum EXrpOrderType {
  native = "native",
  asset = "asset",
  setTrustLine = "setTrustLine",
  removeTrustLine = "removeTrustLine",
  changeThreshold = "changeThreshold",
  updateOwners = "updateOwners",
  updateOwnerThreshold = "updateOwnerThreshold",
}

interface XrpNativeInstruction {
  type: EXrpOrderType.native;
  to: string;
  amount: string;
  memo?: string;
}
interface XrpAssetInstruction {
  type: EXrpOrderType.asset;
  to: string;
  amount: string;
  tokenAddress: string; // "hexCurrency.issuerAddress"
  memo?: string;
}
interface XrpSetTrustLineInstruction {
  type: EXrpOrderType.setTrustLine;
  tokenAddress: string;
  issuer: string;
  limit?: string;
} // default "1000000000"
interface XrpRemoveTrustLineInstruction {
  type: EXrpOrderType.removeTrustLine;
  currency: string;
  issuer: string;
}
interface XrpChangeThresholdInstruction {
  type: EXrpOrderType.changeThreshold;
  newThreshold: number;
}
interface XrpUpdateOwnersInstruction {
  type: EXrpOrderType.updateOwners;
  newOwners: string[];
}
interface XrpUpdateOwnerThresholdInstruction {
  type: EXrpOrderType.updateOwnerThreshold;
  newOwners: string[];
  newThreshold: number;
}

type TXrpCommandInstruction =
  | XrpNativeInstruction
  | XrpAssetInstruction
  | XrpSetTrustLineInstruction
  | XrpRemoveTrustLineInstruction
  | XrpChangeThresholdInstruction
  | XrpUpdateOwnersInstruction
  | XrpUpdateOwnerThresholdInstruction;
```

An XRP order also carries a single instruction. `method: 'xrp_native'` and `method: 'native'` are
equivalent. XRP vaults support at most 8 signers, and an owner update above that is rejected
before any request is made.

## Creating orders

### `createSendOrder`, the simple path

```typescript theme={null}
const { order } = await sdk.createSendOrder({
  multisigId: multisig.id,
  walletAddress: myWalletAddress,
  to: recipientAddress,
  amount: "0.5",
  chainId: "1",
  // token: { address, symbol, decimals }, omit for the native asset
  // memo: 'invoice 42'
  userRef,
});
```

It looks the vault up to learn its `chainType`, validates `to` against that chain, picks the method
for you (`native` with no token; otherwise `erc20` on EVM, `spl` on SOL, `trc20`/`trc` on TRON by
address prefix, `asset` on XRP), builds a one-element instruction list, and delegates to
`createOrder`. It throws `InvalidInputError` for a token transfer on a chain it cannot map
(`Unsupported chain for token transfer`).

### `createOrder`, full control

```typescript theme={null}
const { order } = await sdk.createOrder({
  chain: EChainType.EVM,
  multisigId: multisig.id,
  walletAddress: myWalletAddress,
  method: "erc20",
  instructions: [
    {
      to: recipientAddress,
      amount: "100",
      tokenAddress: "0xA0b8...",
      decimals: 6,
    },
  ],
  token: { address: "0xA0b8...", symbol: "USDC", decimals: 6 },
  userRef,
});
```

`CreateOrderOutput` is `{ order: Order; proposalHash?: string }`. `proposalHash` is populated on
Solana only, with the `multisigPda`.

### `createSettingsOrder`, owners and threshold

```typescript theme={null}
const { order } = await sdk.createSettingsOrder({
  multisigId: multisig.id,
  multisig, // current vault state, required
  walletAddress: myWalletAddress,
  newOwners: [...currentOwners, "0x4444..."], // full desired final list
  newThreshold: 3, // optional
  userRef,
});
```

The diff against `multisig.owners` / `multisig.threshold` decides the method. See the change-method
table on the [Multisig Vaults](https://sdk-guide.mintlify.site/sdk-guide/multisig.md#changing-owners-and-threshold) page. No change
throws `InvalidInputError: No changes detected in multisig configuration`.

> [!WARNING]
>
> Bitcoin and Cardano vaults have no settings orders, because their owner set and
> threshold are fixed in the script at creation.

## Signing

```typescript theme={null}
const result = await sdk.signOrder({
  chain: EChainType.EVM,
  orderId: order.id,
  order, // pass the order object you displayed to the user
  walletAddress: myWalletAddress,
  multisigId: multisig.id,
  userRef,
});

result.signature; // chain-specific signature / signed payload
result.order; // refreshed order with the new signature attached
```

### What differs per chain

`signOrder` is one call everywhere, but the cost and the effect differ:

| Chain | Costs the signer | Notes |
| - | - | - |
| EVM | nothing | An off-chain signature; no transaction is broadcast |
| Solana | a network fee | The approval is its own on-chain transaction |
| Tron | nothing | The transaction to sign is built server-side; fetching it also starts the order's expiry clock |
| Bitcoin | nothing | Adds a signature to the shared transaction |
| Cardano | nothing | Adds a witness. The order you pass must carry `multisig` — see below |
| XRP | nothing | Uses optimistic locking: if another owner signs first, the SDK re-reads and retries |

In every case the SDK checks what it is about to sign against what the order declares before the key
is reconstructed. See below.

> [!WARNING]
>
> **Cardano: pass the order with `multisig` populated.** A Cardano transaction pays the recipient
> and returns the remainder to the vault, and the SDK needs the vault address to tell those two
> outputs apart. It will not fetch that address itself: the backend supplied the transaction body,
> so letting it also declare which output is "change" would defeat the check. Without it, signing
> fails with *"ADA order carries no vault address"*.
>
> `multisig` is optional on `Order` because the paginated list omits it. Read the order through
> `getOrderById`, drop `order` from the input and let the SDK fetch it, or merge in the vault you
> already hold:
>
> ```typescript theme={null}
> await sdk.signOrder({ ...input, order: { ...orderFromList, multisig } });
> ```

### Signing safety

Before the private key is touched, `signOrder` checks the payload about to be signed against what
the order declares. A disagreement throws `SignatureIntentMismatchError`; see
[Errors](https://sdk-guide.mintlify.site/sdk-guide/errors.md#intent-mismatch) for the message shapes and
[Preflight](https://sdk-guide.mintlify.site/sdk-guide/preflight.md#signing-time-integrity-checks) for the full set of checks that run
at signing time on every chain. Treat this error as a security event, not a transient failure, and
do not retry it.

For a simulation of what an order would actually do on chain, run
[`preflightOrder`](https://sdk-guide.mintlify.site/sdk-guide/preflight) first. It returns `null` when no check covers the
chain or method, so gate on `preflightBlocksSigning(result, isRunning)` rather than on truthiness.

## Rejecting

```typescript theme={null}
const rejected = await sdk.rejectOrder({
  chain: EChainType.EVM,
  order,
  multisig, // required
  walletAddress: myWalletAddress,
  userRef,
});
```

`rejectOrder` resolves to the resulting `Order`.

| Chain | What actually happens |
| - | - |
| EVM | Creates a counter-order at the same nonce. Executing it burns the nonce and invalidates the original |
| Solana | Records an on-chain rejection; the signer pays the network fee |
| Everything else | Records the rejection against the order |

`walletAddress` and the vault id are required. A rejection missing either fails immediately, before
any request.

## Executing

```typescript theme={null}
const result = await sdk.executeOrder({
  chain: EChainType.EVM,
  order,
  multisig,
  walletAddress: myWalletAddress,
  userRef,
  // feeQuote?: EstimatedFee, gasLimit/gasPrice override on EVM
});

result.txHash;
result.order;
```

| Chain | Who pays the network fee | Before it runs |
| - | - | - |
| EVM | The executor's wallet | The SDK checks the executor can cover the gas and throws `InsufficientBalanceError` rather than letting a doomed execution proceed |
| Solana | The executor's wallet | Refuses below 0.001 SOL |
| Tron | The vault's balance | The vault must hold enough TRX; settings orders add a flat 100 TRX |
| Bitcoin | The vault's UTXOs | The fee is already part of the transaction |
| Cardano | The vault's balance | |
| XRP | The vault's balance | The vault must stay above its reserve |

> [!TIP]
>
> On EVM and Solana, fund the acting owner's personal wallet. On Tron, Bitcoin, Cardano and XRP,
> fund the vault. See [Multisig Fees](https://sdk-guide.mintlify.site/sdk-guide/multisig-fees.md).

## Chain-specific extras

### Solana: retrying a failed activation

Creating a Solana order takes two steps: the order is registered, then activated on chain. An order
can therefore exist with its activation incomplete. Pass `orderIdToActivate` on `createOrder` to
re-run only the activation for such an order instead of creating a new one.

### Cancelling every pending order on an EVM vault

Rejecting an EVM order creates a counter-order at the same nonce; executing that counter-order
invalidates every pending order sharing that nonce. There is no separate "cancel all".

### Deleting an order record

`deleteOrder` removes the order record. It does not affect anything on chain; use `rejectOrder` for
that. The server deletes only an order that is still `created` or `proposed`; for any other status
the call fails with an `ApiError`.

```typescript theme={null}
await sdk.deleteOrder({
  chain: EChainType.EVM,
  orderId: order.id,
});
```

## Reading orders

```typescript theme={null}
const page = await sdk.getOrders({
  chain: EChainType.EVM,
  multisigId: multisig.id,
  page: 1,
  limit: 20,
});

const { order } = await sdk.getOrderById({
  chain: EChainType.EVM,
  orderId: "uuid",
});
```

`getOrders` defaults to `page: 1, limit: 20`.

> [!NOTE]
>
> The two read methods are easy to mix up. `getOrders` takes a multisig id and returns that
> vault's page of orders; `getOrderById` takes an order id and returns one order.

## History

```typescript theme={null}
const vaultHistory = await sdk.getMultisigTxHistory({
  multisigId: multisig.id,
  page: 1,
  limit: 20,
});

const allHistory = await sdk.getAllMultisigsTxHistory({
  page: 1,
  limit: 20,
  filter: "all", // all | pending | executed | rejected  (StatusFilterEnum)
  hideDust: true, // hide transfers under $1
});
```

Both return `TxHistoryCombinedResponse`:

```typescript theme={null}
interface TxHistoryCombinedResponse {
  items: TxHistoryItem[];
  limit: number;
  total: number;
  page: number;
}
```

Personal wallet history (`getWalletHistory`) returns a different shape:

```typescript theme={null}
interface HistoryResponse {
  total: number;
  page: number;
  limit: number;
  items: TransactionHistoryItem[];
}

interface TransactionHistoryItem {
  chainType: EChainType;
  chainId: ChainIdValue;
  threshold: string;
  executed: string;
  status: "success" | "failed" | "pending";
  method: string;
  from: string;
  to: string;
  value: string;
  creator: string;
  confirmations: string[];
  token: TokenData;
  details: TransactionDetails;
  hash: string;
  time: string;
}
```

## Organization orders

Every order method takes `walletScope`. With `walletScope: 'organization'` the same call operates on
an org-owned vault instead of a personal one; the request and response shapes are identical. See
[Organization Vaults](https://sdk-guide.mintlify.site/sdk-guide/organization-vaults.md#organization-orders).

## Complete flow

```typescript theme={null}
await sdk.runWithUserRef(userRef, async () => {
  const { order } = await sdk.createSendOrder({
    multisigId: multisig.id,
    walletAddress: owner1,
    to: recipient,
    amount: "1.0",
    chainId: "1",
  });

  const preflight = await sdk.preflightOrder({ order, multisig });
  if (preflightBlocksSigning(preflight, false)) return;

  const first = await sdk.signOrder({
    chain: EChainType.EVM,
    orderId: order.id,
    order,
    walletAddress: owner1,
    multisigId: multisig.id,
  });

  const second = await sdk.signOrder({
    chain: EChainType.EVM,
    orderId: order.id,
    order: first.order,
    walletAddress: owner2,
    multisigId: multisig.id,
  });

  if (second.order.signatures.length >= Number(multisig.threshold)) {
    const { txHash } = await sdk.executeOrder({
      chain: EChainType.EVM,
      order: second.order,
      multisig,
      walletAddress: owner2,
    });
    console.log("executed", txHash);
  }
});
```

`multisig.threshold` is a string on `MultisigsItem`, so coerce it before comparing.

## Next

> [card] [Multisig Vaults](https://sdk-guide.mintlify.site/sdk-guide/multisig.md)
>
> Creating and activating the vault an order runs on.

> [card] [Multisig Network Fees](https://sdk-guide.mintlify.site/sdk-guide/multisig-fees.md)
>
> Who pays at each lifecycle step, per chain.

> [card] [Preflight](https://sdk-guide.mintlify.site/sdk-guide/preflight.md)
>
> Simulate before you collect a signature.

> [card] [Errors](https://sdk-guide.mintlify.site/sdk-guide/errors.md)
>
> `SignatureIntentMismatchError` and the rest of the taxonomy.


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