Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

`@mirageprivacy/sdk` is the TypeScript SDK for private transfers through Mirage. It coordinates wallet approvals, API-authored pricing, EscrowBatch deployment, compliance approval, encrypted Signal submission to Nomad, and recipient-transfer polling.
`@mirageprivacy/sdk` is the TypeScript SDK for private transfers through Mirage. It coordinates wallet approvals, API-authored pricing, escrow deployment, compliance approval, encrypted Signal submission to Nomad, and recipient-transfer polling.

The API owns pricing and funding calculations. Nomad verifies the signed pricing and compliance authorizations and performs private execution. The SDK must not recreate the private platform/node fee split or accept executable economics from the application.

Expand Down Expand Up @@ -84,12 +84,12 @@ The SDK implements the following private transfer flow:
1. **Prepare**: Preserve row order, group rows into Signals by asset, fetch Nomad's attested network key, and derive one blinded signer per row.
2. **Quote**: Send the chain, sender, ordered Signals, execution modes, and blinded signers to `/pricing/quote`.
3. **Approve**: Approve each non-native asset using the exact amount returned in `depositByAsset`.
4. **Deploy**: Deploy the API-provided EscrowBatch constructor with the exact quoted native `msgValue`.
4. **Deploy**: Deploy the API-provided escrow constructor with the exact quoted native `msgValue`.
5. **Compliance**: Submit the deployment transaction and quote commitment to `/compliance` and receive a quote-bound execution approval.
6. **Signal**: Encrypt a minimal Signal envelope with Nomad's attested network key and submit it to `/signal`.
7. **Complete**: Poll and emit each recipient delivery, followed by the final completion event.

Every transfer uses `EscrowBatch`, including a one-row transfer. The SDK does not fall back to the legacy ERC-20 or native escrow formats.
A one-row ERC-20 request uses `EscrowERC20`, a one-row native request uses `EscrowNative`, and two or more rows use `EscrowBatch`. The selected type is sent to both pricing and obfuscation and is retained for compliance and resume.

### Pricing and Signal Construction

Expand All @@ -106,7 +106,7 @@ The pricing response provides:
- Reward asset and reward amount
- Exact deposits required per asset
- Exact native `msgValue`
- Exact EscrowBatch constructor arguments
- Exact constructor arguments for the selected escrow
- Quote commitment
- Pricing authorization sealed directly for Nomad

Expand Down Expand Up @@ -152,8 +152,8 @@ The encrypted Nomad envelope contains the escrow address, base blinding scalar,

**Blinded signers** (`src/internal/bond.ts`)

- Generates the local batch scalar
- Derives one ordered blinded signer for every row
- Generates the local blinding scalar
- Derives one signer for a single escrow or one ordered blinded signer per batch row

**Polling** (`src/internal/poll.ts`)

Expand Down Expand Up @@ -193,7 +193,7 @@ Public interfaces live in `src/types.ts` and are exported through `src/index.ts`

- **API-owned pricing**: Never calculate the platform fee, node fee, reward pot, floor, ceiling, gas buffer, or capital component in the SDK.
- **Exact deployment**: Approval amounts, constructor arguments, and `msgValue` must come directly from the quote used for that deployment.
- **One-row batches**: A single transfer is the `n = 1` EscrowBatch case, not a separate protocol path.
- **Escrow selection**: One ERC-20 row uses `EscrowERC20`, one native row uses `EscrowNative`, and multiple rows use `EscrowBatch`.
- **Row ordering**: Reordering rows changes signer derivation, Signal grouping, and potentially the reward denomination. Preserve the caller's order.
- **No linked mode**: Do not add linked execution to API requests or Nomad Signals.
- **Attestation hash**: The payload commitment is `sha256(publicKey . chainId_be . maxBalanceUsd_be . complianceKeys . pricingKeys)`. Preserve both signer arrays in served order.
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @mirageprivacy/sdk

TypeScript SDK for private transfers on the Mirage protocol. It requests API-authored pricing, deploys the exact quoted EscrowBatch, obtains a quote-bound execution approval, submits the encrypted Nomad Signal, and polls recipient transfers.
TypeScript SDK for private transfers on the Mirage protocol. It requests API-authored pricing, deploys the exact quoted escrow, obtains a quote-bound execution approval, submits the encrypted Nomad Signal, and polls recipient transfers.

## Install

Expand Down Expand Up @@ -88,7 +88,7 @@ for await (const event of prepared.complete(walletClient, deployed.secrets)) {
```

`ApprovalCheckpoint` and `TransferSecrets` are serializable stage boundaries.
The latter includes the batch scalar, quote commitment, and opaque sealed
The latter includes the blinding scalar, quote commitment, and opaque sealed
pricing authorization. Persist it immediately: a reload must submit the same
authorization that produced the deployed constructor.

Expand Down Expand Up @@ -120,11 +120,13 @@ now requires an API server with the proxy configured.

1. **fees** - Public API service fee and exact funding requirements
2. **approve** - One exact approval per ERC-20 funding asset
3. **deploy** - Exact API-quoted EscrowBatch deployment
3. **deploy** - Exact API-quoted escrow deployment
4. **compliance** - Execution approval bound to the deployment and quote
5. **signal** - Minimal encrypted Signal envelope submission to Nomad
6. **complete** - Transfer event observed on-chain

One ERC-20 row deploys `EscrowERC20`, one native row deploys `EscrowNative`, and two or more rows deploy `EscrowBatch`.

### Cancellation

Pass an `AbortSignal` to cancel mid-transfer:
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mirageprivacy/sdk",
"version": "0.3.2",
"version": "0.4.0",
"description": "SDK for private transfers on Mirage",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
6 changes: 3 additions & 3 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ export class WhitelistRequiredError extends MirageError {
}

/**
* Thrown when a priced EscrowBatch is resumed without its base blinding
* scalar. Nomad cannot derive the constructor's one-time bid signers without
* it, so completion must use the secrets retained at deployment.
* Thrown when a priced escrow is resumed without its blinding scalar. Nomad
* cannot derive its one-time signer or batch signer set without it, so
* completion must use the secrets retained at deployment.
*/
export class MissingBlindingScalarError extends MirageError {
escrowAddress?: Address;
Expand Down
9 changes: 5 additions & 4 deletions src/internal/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export interface PricingQuote {
chainId: number;
serviceFee: { asset: Address; amount: bigint };
deployment: {
escrowType: "batch";
escrowType: EscrowKind;
constructorArgs: `0x${string}`;
quoteCommitment: `0x${string}`;
rewardAsset: Address;
Expand All @@ -120,12 +120,13 @@ export interface PricingQuote {
sealedPricingAuthorization: `0x${string}`;
}

/** Request the API-authored economics and exact EscrowBatch constructor. */
/** Request the API-authored economics and exact escrow constructor. */
export async function fetchPricingQuote(
apiServer: string,
params: {
chainId: number;
sender: Address;
escrowType: EscrowKind;
blindedSigners: Address[];
signals: PricingSignalRequest[];
},
Expand All @@ -134,7 +135,7 @@ export async function fetchPricingQuote(
chain_id: number;
service_fee: { asset: Address; amount: string };
deployment: {
escrow_type: "batch";
escrow_type: EscrowKind;
constructor_args: `0x${string}`;
quote_commitment: `0x${string}`;
reward_asset: Address;
Expand All @@ -149,7 +150,7 @@ export async function fetchPricingQuote(
body: JSON.stringify({
chain_id: params.chainId,
sender: params.sender,
escrow_type: "batch",
escrow_type: params.escrowType,
blinded_signers: params.blindedSigners,
signals: params.signals,
}),
Expand Down
15 changes: 8 additions & 7 deletions src/internal/bond.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { publicKeyToAddress } from "viem/utils";
import { secp256k1 } from "@noble/curves/secp256k1.js";
import { MirageError } from "../errors.js";

export interface BatchBlindedSigners {
/** Ordered one-time bid signers committed into EscrowBatch. */
export interface BlindedSigners {
/** One signer for a single escrow or ordered one-time signers for EscrowBatch. */
blindedSigners: Address[];
/** Base scalar needed by Nomad to derive each corresponding private key. */
/** Scalar needed by Nomad to derive each corresponding private key. */
blindingScalar: `0x${string}`;
}

Expand All @@ -17,16 +17,17 @@ function toHex(bytes: Uint8Array): string {
}

/**
* Derive `G + (s + i)B` for every batch row from one fresh base scalar.
* Derive `G + (s + i)B` for each escrow signer from one fresh base scalar.
* A single escrow uses only index zero, which reduces to `G + sB`.
* Nomad receives only `s` inside the encrypted Signal; the pricing API receives
* only the resulting public addresses.
*/
export function deriveBatchBlindedSigners(
export function deriveBlindedSigners(
globalKeyHex: string,
signerCount: number,
): BatchBlindedSigners {
): BlindedSigners {
if (!Number.isSafeInteger(signerCount) || signerCount < 1) {
throw new MirageError("INVALID_PARAMS", "At least one batch signer is required");
throw new MirageError("INVALID_PARAMS", "At least one escrow signer is required");
}

let globalPoint;
Expand Down
4 changes: 3 additions & 1 deletion src/internal/nomad.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Address } from "viem";
import { ApiError, MissingBlindingScalarError } from "../errors.js";
import { nomadProxyUrl } from "./api.js";
import type { ExecutionApproval, NetworkKeyStatus } from "../types.js";
import type { EscrowKind, ExecutionApproval, NetworkKeyStatus } from "../types.js";

async function encryptSignal(payload: Uint8Array, publicKeyHex: string): Promise<Uint8Array> {
const { encrypt } = await import("eciesjs");
Expand All @@ -15,6 +15,7 @@ function toHexString(bytes: Uint8Array): string {
}

export interface SignalParams {
escrowType: EscrowKind;
escrowAddress: Address;
blindingScalar: `0x${string}`;
sealedPricingAuthorization: `0x${string}`;
Expand All @@ -36,6 +37,7 @@ export async function submitSignal(params: SignalParams): Promise<string> {
}

const signal = {
escrowType: params.escrowType,
escrowContract: params.escrowAddress,
blindingScalar: params.blindingScalar,
sealedPricingAuthorization: params.sealedPricingAuthorization,
Expand Down
Loading
Loading