npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sbtc-escrow-sdk

v0.3.1

Published

TypeScript SDK for the sBTC Escrow smart contract on Stacks (STX + sBTC, testnet and mainnet)

Downloads

616

Readme

sBTC Escrow SDK

TypeScript SDK for the sBTC Escrow smart contracts on Stacks. Supports both STX (native) and sBTC (SIP-010 fungible token) escrows across the full contract version history.

| Network | Active contract | Legacy (read/act on existing escrows) | | --- | --- | --- | | Mainnet | escrow-mainnet-v3 | escrow-mainnet-v2 | | Testnet | escrow-v8 (v3-equivalent) | escrow-v7 |

v3 features: burn-block-anchored expiry (Bitcoin-block timing, stable ~10 min/block), beneficiary delegation (third-party with buyer-equivalent rights), seller self-rescue after 2× dispute timeout, time-bounded admin pause with anti-chaining cooldown, sweep-orphans for misdirected funds, and partial dispute resolution. See What's new in v3 at the bottom.

Installation

npm install sbtc-escrow-sdk @stacks/transactions @stacks/network

Quick Start

import { EscrowClient, EscrowStatus, TokenType } from 'sbtc-escrow-sdk';

// Initialize client
const client = new EscrowClient({ network: 'testnet' });

// Read escrow data
const escrow = await client.getEscrow(1);
if (escrow) {
  const label = escrow.tokenType === TokenType.SBTC ? 'sBTC' : 'STX';
  console.log(`${escrow.amount} ${label} — ${EscrowStatus[escrow.status]}`);
}

// Get platform stats (per-token volumes)
const stats = await client.getPlatformStats();
console.log(`STX volume: ${stats.totalVolumeStx}, sBTC volume: ${stats.totalVolumeSbtc}`);

Usage

Read-Only Operations

const client = new EscrowClient({ network: 'testnet' });

// Get contract configuration (per-token bounds)
const config = await client.getConfig();
console.log(`Fee: ${config.platformFeeBps / 100}%`);
console.log(`STX bounds: ${config.minAmountStx}–${config.maxAmountStx}`);
console.log(`sBTC bounds: ${config.minAmountSbtc}–${config.maxAmountSbtc}`);
console.log(`Dispute timeout: ${config.disputeTimeout} blocks`);

// Get escrow by ID
const escrow = await client.getEscrow(1);
if (escrow) {
  console.log(`Token: ${TokenType[escrow.tokenType]}`);
  console.log(`Status: ${EscrowStatus[escrow.status]}`);
  console.log(`Disputed at: ${escrow.disputedAt}`);
}

// Get user statistics (per-token sent/received)
const userStats = await client.getUserStats('ST1HK6H018TMMZ1BZPS1QMJZE9WPA7B93T8ZHV94N');
console.log(`STX sent: ${userStats.totalSentStx}, sBTC sent: ${userStats.totalSentSbtc}`);

// Calculate fee
const fee = await client.calculateEscrowFee(1_000_000); // 1 STX
console.log(`Fee for 1 STX: ${fee / 1_000_000} STX`);

// Check contract state
const paused = await client.isPaused();
const expired = await client.isExpired(1);
const timedOut = await client.isDisputeTimedOut(1);
const status = await client.getStatus(1);
const role = await client.getUserRole(1, 'ST...');

Write Operations (Require Private Key)

const client = new EscrowClient({ network: 'testnet' });
const senderKey = 'your-private-key-hex';

// Create an STX escrow
const result = await client.createEscrow(
  {
    seller: 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG',
    amount: 1_000_000, // 1 STX
    description: 'Payment for services',
    durationBlocks: 144, // ~24 hours
    tokenType: TokenType.STX,
  },
  { senderKey }
);

// Create an sBTC escrow
const btcResult = await client.createEscrow(
  {
    seller: 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG',
    amount: 100_000, // 0.001 BTC
    description: 'BTC payment',
    durationBlocks: 144,
    tokenType: TokenType.SBTC,
  },
  { senderKey }
);

if (result.success) {
  console.log(`TX: ${result.txid}`);
  console.log(`Explorer: ${client.getExplorerTxUrl(result.txid)}`);
}

// Release funds to seller (buyer only)
await client.release(1, { senderKey });

// Refund to buyer (seller voluntarily, or anyone after expiry on v6 / buyer
// after expiry + review window on v7+)
await client.refund(1, { senderKey });

// Dispute an escrow (buyer or seller)
await client.dispute(1, { senderKey });

// Extend escrow expiry (buyer only, before expiry)
await client.extendEscrow(1, 144, { senderKey });

// Resolve expired dispute (buyer self-service after timeout)
await client.resolveExpiredDispute(1, { senderKey });

v7+ Operations

These methods are available on escrow-v7 and later contracts (escrow-v8, escrow-mainnet-v2, escrow-mainnet-v3). Older contracts (escrow-v6, escrow-mainnet) don't expose them. Gate with supportsV3Features(contractId) or its v7+ equivalent if you target multiple contract versions.

// Seller signals delivery on-chain. Flips status to DELIVERED and starts
// the review window during which the buyer cannot unilaterally refund
// without raising a dispute first. Proof on-chain that work was delivered.
await client.deliver(1, { senderKey });

// Check whether an escrow is currently inside its post-delivery review
// window. Returns true only after deliver() was called and before the
// review period elapsed.
const inReview = await client.isInReviewPeriod(1);

// Admin/arbiter: resolve a disputed escrow with a partial split.
// `buyerBps` is the buyer's share of the principal in basis points (0–10000).
// Seller gets the remainder. Fee is split pro-rata.
//   buyerBps = 0      → identical to resolve-dispute-for-seller
//   buyerBps = 10000  → identical to resolve-dispute-for-buyer
//   buyerBps = 7000   → 70% to buyer, 30% to seller
await adminClient.resolveDisputeSplit(1, 7000, { senderKey: adminKey });

v3+ Operations

These methods are only available on escrow-mainnet-v3 (mainnet) and escrow-v8 (testnet). Gate with supportsV3Features(contractId):

import { supportsV3Features } from 'sbtc-escrow-sdk';

if (supportsV3Features(contractId)) {
  // Create with optional beneficiary — a third party with buyer-equivalent
  // release/refund/dispute rights. Useful for marketplace flows where the
  // platform itself can act on behalf of the buyer.
  await client.createEscrow({
    seller: 'SP…',
    amount: 100_000_000n,
    duration: 4320,                         // burn blocks (~30 days mainnet)
    description: 'Logo design',
    tokenType: 'STX',
    beneficiary: 'SP_PLATFORM…',            // optional
  }, { senderKey });

  // Seller self-rescue: only callable on a DELIVERED escrow after 2× the
  // dispute timeout has elapsed. Lets a seller recover funds when admin
  // has not resolved a disputed-but-delivered escrow. Read-only check
  // first to gate the UI button.
  const eligible = await client.isSellerRescueEligible(1);
  if (eligible) {
    await client.resolveExpiredDisputeForSeller(1, { senderKey });
  }

  // Admin sweep of orphan funds — recovers tokens that landed at the
  // contract address outside any escrow (misdirected transfers). The
  // contract enforces `amount ≤ free_balance` where free_balance =
  // contract_balance - total_locked, so active escrows are untouchable.
  await client.sweepOrphans('STX', 1_000_000n, { senderKey: adminKey });
}

Admin Operations

const adminKey = 'admin-private-key';

// Resolve dispute for buyer (refund)
await client.resolveDisputeForBuyer(1, { senderKey: adminKey });

// Resolve dispute for seller (release)
await client.resolveDisputeForSeller(1, { senderKey: adminKey });

// Update platform fee (max 500 = 5%)
await client.setPlatformFee(100, { senderKey: adminKey }); // 1%

// Update fee recipient
await client.setFeeRecipient('ST...', { senderKey: adminKey });

// Update dispute timeout (1–57600 blocks)
await client.setDisputeTimeout(28800, { senderKey: adminKey });

// Pause / unpause contract
//   v6 / escrow-mainnet: pauseContract takes no args (indefinite pause).
//   v3+ (escrow-mainnet-v3, escrow-v8): pauseContract requires `durationBlocks` —
//   the contract auto-unpauses after that many burn blocks AND blocks any
//   re-pause attempt until 2× the duration has elapsed (anti-chaining cooldown).
await client.pauseContract({ senderKey: adminKey }, 144);  // 144 = ~24h mainnet
await client.unpauseContract({ senderKey: adminKey });

// 2-step ownership transfer
await client.transferOwnership('ST_NEW_OWNER...', { senderKey: adminKey });
// New owner calls:
await client.acceptOwnership({ senderKey: newOwnerKey });

Configuration

const client = new EscrowClient({
  network: 'testnet',                   // or 'mainnet'
  contractAddress: 'ST1HK6...',         // optional, uses default
  contractName: 'escrow-v8',            // optional; defaults: 'escrow-v8' (testnet), 'escrow-mainnet-v3' (mainnet)
  sbtcContract: 'ST1F7Q...sbtc-token',  // optional, per-network default
  apiUrl: 'https://api.testnet.hiro.so', // optional
});

Types

TokenType

enum TokenType {
  STX = 0,   // Native Stacks token (6 decimals / microSTX)
  SBTC = 1,  // SIP-010 fungible token (8 decimals / satoshis)
}

EscrowStatus

enum EscrowStatus {
  PENDING = 0,
  RELEASED = 1,
  REFUNDED = 2,
  DISPUTED = 3,
  DELIVERED = 4,   // v7+ only
}

Escrow

interface Escrow {
  id: number;
  buyer: string;
  seller: string;
  amount: number;
  feeAmount: number;
  tokenType: TokenType;
  description: string;
  status: EscrowStatus;
  createdAt: number;   // block height
  expiresAt: number;   // block height
  completedAt: number | null;
  disputedAt: number | null;
  deliveredAt: number | null;   // v7+ only: block height the seller called deliver()
}

PlatformStats

interface PlatformStats {
  totalEscrows: number;
  totalVolumeStx: number;
  totalVolumeSbtc: number;
  totalFeesCollectedStx: number;
  totalFeesCollectedSbtc: number;
  totalReleased: number;
  totalRefunded: number;
  activeDisputes: number;
}

EscrowConfig

interface EscrowConfig {
  owner: string;
  feeRecipient: string;
  platformFeeBps: number;
  isPaused: boolean;
  minAmountStx: number;
  maxAmountStx: number;
  minAmountSbtc: number;
  maxAmountSbtc: number;
  maxDuration: number;
  disputeTimeout: number;
  reviewPeriod?: number;   // v7+ only — blocks after deliver() during which
                           // the buyer cannot unilaterally refund
}

UserStats

interface UserStats {
  escrowsCreated: number;
  escrowsReceived: number;
  totalSentStx: number;
  totalSentSbtc: number;
  totalReceivedStx: number;
  totalReceivedSbtc: number;
}

Contract Addresses

| Network | Active | Legacy (read-only) | | --- | --- | --- | | Mainnet | SP1HK6H018TMMZ1BZPS1QMJZE9WPA7B93TA2BMTGA.escrow-mainnet-v3 | SP1HK6H018TMMZ1BZPS1QMJZE9WPA7B93TA2BMTGA.escrow-mainnet-v2 | | Testnet | ST1HK6H018TMMZ1BZPS1QMJZE9WPA7B93T8ZHV94N.escrow-v8 | ST1HK6H018TMMZ1BZPS1QMJZE9WPA7B93T8ZHV94N.escrow-v7 |

Legacy contracts remain readable and actionable (release / refund / dispute / resolve) for existing escrows — the SDK auto-dispatches by contractId. New escrows should target the active contract on each network.

What's new in v3

The escrow-mainnet-v3 / escrow-v8 contracts add six material changes over v2/v7. The SDK handles all of these — gate on supportsV3Features(contractId) in your client code where you need to surface them in the UI.

1. Burn-block-anchored expiry (Bitcoin-block timing)

v2/v7 stored expiry as stacks-block-height. With variable Stacks block production (~5s to ~2min post-Nakamoto), a "30-day" escrow could resolve in 20 or 40 days depending on chain conditions. v3 anchors all time fields to burn-block-height — Bitcoin's ~10-min cadence, stabilized by difficulty retarget over each 2016-block epoch. Predictable, regardless of Stacks side.

2. Beneficiary delegation

create-escrow accepts an optional beneficiary principal — a third party with the same release/refund/dispute rights as the buyer. Useful for marketplace flows where the platform itself can act on behalf of the buyer, or escrows held on behalf of a DAO/multisig.

3. Seller self-rescue (resolve-expired-dispute-for-seller)

If a seller delivered (signaled via deliver()) but the buyer then disputed AND the admin/arbiter failed to resolve within 2× the dispute timeout, the seller can recover funds themselves. Prevents admin-griefing of legitimate deliveries.

4. Time-bounded admin pause with anti-chaining cooldown

pause-contract(duration) now requires a duration. The contract auto-unpauses after duration burn blocks, and a re-pause attempt is rejected with ERR_PAUSE_COOLDOWN_ACTIVE (u4003) until 2 * duration blocks elapse. Manual unpause-contract lifts the pause early but does NOT reset the cooldown. Closes an indefinite-pause griefing vector found in the v3 audit.

5. Sweep-orphans for misdirected funds

If someone sends STX or sBTC directly to the contract principal (outside any escrow), sweep-orphans(token-type, amount) lets the admin recover those funds to the fee-recipient. Locked-balance accounting prevents the sweep from touching active escrow funds: amount ≤ contract_balance - total_locked.

6. Per-escrow fee-recipient snapshot + partial dispute resolution

Each escrow snapshots the fee-recipient at create time, so changing the platform-level fee-recipient mid-stream doesn't affect in-flight escrows. v7+ resolve-dispute-split(escrow-id, buyer-bps) is fully supported on v3 too, with the fee deducted pro-rata from each side.

License

MIT