Skip to main content

Overview

In April 2026, Context Markets deployed upgraded versions of the Holdings and Settlement contracts. These changes make market order execution and quoting significantly smoother by simplifying contract logic and removing unnecessary constraints. Users are automatically migrated through a built-in flow in the app. This guide is for developers integrating the migration process into custom applications.

Migration process

The SDK handles migrations via the migration module. Here’s the flow:

1. Check migration status

const status = await client.migration.getStatus({ address: walletAddress });

// Returns:
// - legacyBalances: Assets in old contracts
// - newBalances: Assets already in new contracts
// - fundsMigrationPlan: Chunked USDC migration plan
// - pendingRestorations: Legacy orders ready to restore
// - sponsoredFundsMigrationAvailable: Whether relayer will sponsor gas

2. Initiate migration

const result = await client.migration.start({
  address: walletAddress,
  authorization: signedAuth, // Optional: authorize on behalf of user
});

3. Migrate funds (sponsored)

If sponsoredFundsMigrationAvailable is true, funds can be migrated gaslessly:
// Sign the migration
const signedMigration = await client.migration.signSponsoredMigrateFunds();

// Submit for relayer to execute
const result = await client.migration.migrateFunds(signedMigration);
Without sponsorship, use direct signatures:
// Manual batch withdraw to new Holdings and operator approval
const signed = await client.migration.signSponsoredMigrateFunds(status);

4. Restore legacy orders

Legacy limit orders can be restored as new Settlement V2 orders:
// Get pending restorations
const status = await client.migration.getStatus();
const pending = status.pendingRestorations;

// Sign and submit restorations
const result = await client.migration.restorePendingOrders(pending);

// Or restore specific orders
const filtered = pending.filter(r => selectedOrderIds.includes(r.legacyOrderId));
await client.migration.restorePendingOrders(filtered);

5. Dismiss orders (optional)

If a user wants to skip migrating specific legacy orders:
await client.migration.dismissOrders({
  address: walletAddress,
  legacyOrderIds: [1, 2, 3], // Specific orders to dismiss
  authorization: signedAuth, // Optional
});

What changed

Holdings

Function signature updates:
FunctionV1V2
deposit(address to, address token, uint256 amount)(address to, address token, uint256 amount)
depositWithPermit(address from, address to, ...)(address depositor, address token, uint256 amount, ...)
batchDeposit(address to, ...)(address to, ...)
New functions added:
  • batchTransferFrom() — Execute batch transfers on behalf of users via EIP-712 signature
  • batchDepositMultipleRecipients() — Batch deposit to multiple recipients in one transaction

Settlement V2

Core semantic shift: Orders are explicitly typed instead of implicitly constrained. Buy orders are budget-native instead of price/size-native. Inventory mode replaced with explicit order kinds:
ConstantV1V2
Mode/KindINVENTORY_MODE_ANYORDER_KIND_BUY
INVENTORY_MODE_REQUIRE_HAS_INVENTORYORDER_KIND_SELL_INVENTORY
INVENTORY_MODE_REQUIRE_NO_INVENTORYORDER_KIND_SELL_NO_INVENTORY
Budget-native buys:
  • V1: buys denominated in price × size (shares), payable amount computed at fill time
  • V2: buys denominated in maxCollateralIn (budget) and minSharesOut (guaranteed output)
Single signed order type:
  • V1: separate SignedOrder and MarketOrderIntent structures
  • V2: one SignedOrder type for both limit and market orders
Consumption accounting is now per-order-kind:
  • BUY orders: consumed = buyerCollateralAmount + fees
  • SELL orders: consumed = shareAmount
Removed: mintAndDepositWithPermit()
  • Use Holdings.depositWithPermit() followed by Settlement.mintCompleteSetsFromHoldings() instead
New typehashes and constants:
  • BURN_FROM_HOLDINGS_TYPEHASH
  • MINT_FROM_HOLDINGS_TYPEHASH
  • ORDER_KIND_BUY
  • ORDER_KIND_SELL_INVENTORY
  • ORDER_KIND_SELL_NO_INVENTORY

SDK API reference

migration.getStatus(request?)

Get current migration state for a wallet. Parameters:
  • address? — Wallet address (defaults to configured address)
Returns:
{
  status: "not_started" | "in_progress" | "completed"
  legacyBalances: { token: string; balance: string }[]
  newBalances: { token: string; balance: string }[]
  fundsMigrationPlan: {
    totalAmount: string
    tokens: { token: string; amount: string }[]
    chunks: { tokens: { token: string; amount: string }[] }[]
  }
  pendingRestorations: {
    legacyOrderId: number
    draft: { /* order draft */ }
  }[]
  sponsoredFundsMigrationAvailable: boolean
  sponsoredRelayerAddress?: string
  newHoldingsOperatorNonce?: number
}

migration.start(request?)

Initiate the migration process. Parameters:
  • address? — Target wallet address
  • authorization? — Signed authorization message (for third-party initiation)

migration.migrateFunds(request)

Submit signed fund migration to relayer or direct execution. Parameters:
  • batchWithdraw — Single or array of signed batch withdrawals
  • setOperator — Signed operator approval for Settlement V2
  • address? — Target wallet address

migration.restoreOrders(request)

Restore legacy limit orders as new Settlement V2 orders. Parameters:
  • restorations — Array of signed order restorations (legacyOrderId + signed order)
  • address? — Target wallet address

migration.dismissOrders(request?)

Mark legacy orders as dismissed (not to be migrated). Parameters:
  • legacyOrderIds? — Specific orders to dismiss (omit to dismiss all)
  • address? — Target wallet address
  • authorization? — Signed authorization message

migration.signSponsoredMigrateFunds(status?)

Sign fund migration for sponsored relayer execution. Parameters:
  • status? — Migration status (fetched if omitted)
Returns:
  • batchWithdraw / chunks — Signed batch withdrawal(s)
  • setOperator — Signed operator approval

migration.signAddressAuthorization(request)

Create a signature authorizing a third party to act on your behalf. Parameters:
  • action — Migration action (“start” | “migrate” | “restore” | “dismiss”)
  • address? — Target wallet address
  • legacyOrderIds? — Specific order IDs this auth covers
  • deadline? — Unix timestamp when auth expires

Contract addresses

Holdings

NetworkVersionAddress
MainnetV20x0000000000CcA5bC44912C63d63e1673FeE923f6
BetaV20xBed9a1A6CB168D60aD2C7770Be6B62bD9244D6d3
DevV20xAbB9B71453F85393020771Ddde3d4cA44F52B9A9

Settlement

NetworkVersionAddress
MainnetV20x00000000008c286A2aaa99c6Be3b3D405A929500
BetaV20xa9b830f4496b88c2d3C103fB96Df8f413031eBDD
DevV20x2E74b22204589279CC61c0994D011bF628F3bE47

Implementation checklist

  • Add @contextwtf/sdk version 0.7.0+ (includes migration module)
  • Call client.migration.getStatus() to check if migration is needed
  • Present migration flow UI (status → approve → sign → confirm)
  • Handle sponsored vs. manual migration based on sponsoredFundsMigrationAvailable
  • For legacy orders, show pendingRestorations and let user select which to restore
  • Test with migration testnet (set chain config to testnet deployment)
  • Monitor OperatorSet event on Holdings to confirm operator approval

Common patterns

Check if wallet needs migration

const status = await client.migration.getStatus({ address });
const needsMigration =
  status.legacyBalances.length > 0 ||
  status.pendingRestorations.length > 0;

Estimate gas (unsponsored migration)

Sponsored migrations are gasless. For manual migration:
  • Batch withdraw: ~200k gas per token batch
  • Operator approval: ~50k gas
  • Order restoration: ~150k gas per order

Handle migration errors

Common errors:
  • "Sponsored migrate-funds is not available" — Relayer unavailable; offer manual flow
  • "Migration status did not include a sponsored relayer address" — Relayer not configured
  • "No funds migration chunks are available" — All funds already migrated
Contact support if migration fails unexpectedly.