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

@getpara/rest-sdk

v3.28.0

Published

Typed server-side SDK for Para REST wallet management and signing

Readme

@getpara/rest-sdk

Typed REST client for Para server-to-server wallet operations.

Use this package when your backend owns the workflow and signs with Para REST API keys. Do not ship it to browsers, React apps, mobile apps, wallets, or any other user-controlled runtime. It sends X-API-Key on every request.

Use @getpara/rest-sdk for API-key-backed pregen or programmatic wallets. Use @getpara/server-sdk when you need Para's share-backed server SDK flows, imported user sessions, or core SDK behavior. Use Web, React, React Native, Swift, or Flutter SDKs for user-facing authentication and wallet UX.

The core client covers Para's public REST wallet surface:

  • wallet creation, listing, lookup, and identifier updates
  • SDK pregen share migration and enclave public-key lookup
  • balances, signing, transfers, fee estimation, faucet requests, and REST transaction history
  • REST-specific headers and behavior: X-API-Key, X-Request-Id, caller-supplied Idempotency-Key, AbortSignal, environment selection, fetch injection, JSON serialization errors, validation errors, and HTTP error mapping

The adapter subpaths expose the signing subset through ethers v6, viem v2, and Solana v2 signer interfaces.

Install

yarn add @getpara/rest-sdk

Install only the peer dependencies for the adapters you use:

yarn add ethers
yarn add viem
yarn add @solana/addresses @solana/keys @solana/signers @solana/transactions

The core client expects Node.js 18+ or an injected fetch.

Client

import { ParaRestClient, ParaRestError } from '@getpara/rest-sdk';

const para = new ParaRestClient({
  apiKey: process.env.PARA_API_KEY!,
  env: 'BETA',
});

const wallet = await para.createWallet({
  type: 'EVM',
  userIdentifier: '[email protected]',
  userIdentifierType: 'EMAIL',
});

const signature = await para.signMessage(wallet.id, { message: 'hello' });

env accepts PROD, BETA, SANDBOX, or { baseUrl }. Pass fetch to inject your runtime's fetch implementation, pass requestIdGenerator if you want to control X-Request-Id values, and pass AbortSignal in call options to cancel in-flight requests.

ParaRestError is thrown for non-2xx HTTP responses and includes status, code, requestId, and the parsed response body. ParaRestValidationError is thrown before fetch for missing local required values. ParaRestSerializationError is thrown before fetch when a request body cannot be JSON serialized.

try {
  await para.getWallet(wallet.id, { signal: AbortSignal.timeout(10_000) });
} catch (error) {
  if (error instanceof ParaRestError) {
    console.error(error.status, error.code, error.requestId, error.body);
  }
}

Signing Behavior

EVM message and typed-data signing send unhashed payloads to REST. Para hashes EIP-191 and EIP-712 payloads server-side before signing. The ethers adapter follows ethers' own typed-data behavior and omits any caller-provided EIP712Domain entry from types before forwarding to REST.

The viem adapter follows viem's signMessage split: string messages are forwarded to REST sign-message, while byte-like messages are hashed locally and forwarded to REST sign-raw. Pass { raw: bytes } when a 0x... string should be treated as bytes instead of text.

EVM transaction signing returns the REST API's signed transaction response. The SDK normalizes the caller's transaction request before sending it, but does not re-derive the signed payload locally or diff every returned field against the request. REST EVM transaction signing supports ordinary legacy and EIP-1559 sends only; contract deployments, access lists, blob transactions, and authorization-list transactions are rejected before calling REST. The viem adapter accepts a chain serializer whose output equals the standard Ethereum serialization, such as on Base and other OP Stack chains, or for standard Celo and ZKsync transactions. Chain-specific transactions, such as OP Stack deposits, Celo feeCurrency transactions, and ZKsync EIP-712 transactions, are rejected before calling REST.

The Solana adapter implements the Solana v2 signer traits by signing message bytes through sign-raw. It returns signature dictionaries and can merge signatures into Solana v2 message and transaction objects. Use para.signTransaction(..., { broadcast: true }) or para.transfer(...) when your backend wants REST to submit a transaction and return a transactionId.

Idempotency-Key is caller-supplied per request:

await para.createWallet(
  {
    type: 'EVM',
    userIdentifier: '[email protected]',
    userIdentifierType: 'EMAIL',
  },
  { idempotencyKey: crypto.randomUUID() },
);

Use an idempotency key when a retry could create duplicate state or submit duplicate signing/broadcast work. The SDK does not generate one for you because it does not retry requests internally.

EVM fee urgency

Set urgency: 'normal' | 'urgent' on a self-funded EVM transfer, transferWithAutoRetry, speedUpRestTransaction, cancelRestTransaction, createYieldDeposit, or createYieldWithdrawal request. Omission means normal. Para selects fresh recommended prices; the SDK forwards the choice without calculating fees.

await para.transfer(wallet.id, {
  to: '0x1111111111111111111111111111111111111111',
  value: '1000000000000',
  chainId: '11155111',
  urgency: 'urgent',
}, { idempotencyKey: 'saved-payment-key' });

Explicit initial prices, including zero, stay pinned. Urgency changes generated prices, not gas units; an inconsistent fee pair is rejected. Urgent does not guarantee confirmation time. Sponsored transfers, non-EVM execution, and caller-built signTransaction/signTransactionWithAutoRetry requests reject urgency. EVM fee estimates return both options and do not take a selector. estimateFee() exposes typed feeOptions.normal and feeOptions.urgent, with their fee-family fields, execution budgets, quote timestamp and block number. These optional fields are absent on non-EVM or older responses.

Durable recovery and yield retain the choice across worker restarts and approval/action phases. Omitted and explicit normal share the legacy durable identity; changing urgency under the same key conflicts. Replays retain prepared and signed bytes. Fee ceilings and deadlines remain unchanged; urgent never enlarges spending authorization.

Transaction History

When REST broadcasts a transaction, signTransaction(..., { broadcast: true }) and transfer(...) return a transactionId. Store that id with your own order, payout, or job record so you can reconcile the broadcast later.

const result = await para.signTransaction(wallet.id, {
  transaction,
  broadcast: true,
});

if (result.transactionId) {
  const record = await para.getRestTransaction(wallet.id, result.transactionId);
  console.log(record.status, record.hash);
}

const recent = await para.listRestTransactions(wallet.id, {
  status: 'confirmed',
  limit: 10,
});

Webhooks are the preferred way to receive asynchronous status changes after a broadcast. Treat the REST SDK history methods as the read model: fetch the record immediately after a broadcast, backfill missed webhook deliveries, or reconcile your database against Para's persisted transaction state. See the Para webhook docs for signature verification and rest.transaction.confirmed / rest.transaction.failed payload examples.

Automatic retry for EVM transfers

Use transferWithAutoRetry for self-funded native/ERC-20 EVM transfers. Save the body and key before sending; reuse both after a timeout. HTTP 202 means durable admission before signing or broadcast and returns the original transaction ID and autoRetry state instead of signed bytes.

const key = crypto.randomUUID();
const request = {
  to: '0x1111111111111111111111111111111111111111',
  value: '1000000000000',
  chainId: '11155111',
  urgency: 'urgent' as const,
  autoRetry: {
    maxExecutionFeeWei: '1000000000000000',
    expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
  },
};
// Persist { key, request } before submitting. Never renew the deadline on replay.
const accepted = await para.transferWithAutoRetry(wallet.id, request, { idempotencyKey: key });
const current = await para.getRestTransaction(wallet.id, accepted.transactionId);
console.log(current.status, current.autoRetry?.state);

maxExecutionFeeWei is the per-attempt execution-gas ceiling, excluding separate chain fees. Initial fees must fit; later recommendations remain subject to the original ceiling and historical replacement minimum. This SDK method supports EVM transfers; it does not add urgency to Solana recovery. See Automatic fee retry.

Manual EVM recovery

For an eligible unresolved ordinary self-funded EVM transfer, use speedUpRestTransaction or cancelRestTransaction with a new saved idempotency key and a replacement fee ceiling:

const recovery = await para.speedUpRestTransaction(wallet.id, transactionId, {
  maxExecutionFeeWei: '1000000000000000',
  urgency: 'urgent',
}, { idempotencyKey: recoveryKey });
console.log(recovery.manualRecovery.state, recovery.manualRecovery.attempt.hash);

Both methods return the original transaction ID and typed manualRecovery metadata. Read progress with getRestTransaction. Use cancelRestTransaction with the same body shape to request cancellation. Only one manual speed-up or cancellation is accepted per original transfer; HTTP acceptance does not prove inclusion or cancellation. The original payment can still win. Replaying the saved key/body keeps the selected replacement bytes. See Manual EVM recovery for eligibility.

Automatic retry for broadcast signing

Use signTransactionWithAutoRetry with autoRetry and broadcast: true to authorize recovery of one EVM transaction or supported legacy Solana transaction. Save the key and entire request before submitting; HTTP retries reuse both, including the original deadline. The server returns HTTP 202 before signing or broadcasting, with transactionId, status, and autoRetry instead of signed bytes.

const key = crypto.randomUUID();
const request = {
  transaction: { ...transaction, type: 2 as const },
  broadcast: true as const,
  autoRetry: {
    maxExecutionFeeWei: '1000000000000000',
    expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
  },
};
// Persist { key, request } in your job record before this call.
const result = await para.signTransactionWithAutoRetry(wallet.id, request, { idempotencyKey: key });
const record = await para.getRestTransaction(wallet.id, result.transactionId);
console.log(record.status, record.autoRetry?.state, record.monitoring?.winningHash);

EVM supports type 0 and 2 contract calls, including type 2 access lists. Fees and gas limits use decimal integer strings. Same-nonce replacements preserve the destination, value, calldata, gas limit and access list; fee caps increase by at least 12.5%, rounded up, at least 60 seconds after the last broadcast. There is no fixed attempt limit. maxExecutionFeeWei caps each attempt's execution gas; separate chain fees are excluded.

For Solana, send an unsigned legacy transaction with the wallet as the sole signer and fee payer, a still-valid finalized recent blockhash, exactly one 120000-unit compute limit and one compute-unit-price instruction. Use maxExecutionFeeLamports instead of maxExecutionFeeWei; it caps base plus priority fees and excludes account rent. Versioned and durable-nonce transactions and unsupported system/account instructions are rejected. Para must locate the original hash in its 512-slot finalized history window; missing or older anchor history is rejected before signing. The original transaction bytes are preserved even when a newer finalized hash exists. Solana resends exact bytes until expiry and requires finalized non-execution proof before rebuilding with a new blockhash.

The enrolling secret key, wallet and current policies must authorize every attempt. A transaction-bound approval does not authorize changed fees: retry requests do not accept reviewId or initiatorUserId. A stopped retry can still have a pending on-chain transaction. Continue polling its original ID; do not submit the operation again with a new key. Signer adapters keep their sign-only behavior. See Automatic fee retry.

Yield deposits and withdrawals

Use createYieldDeposit, createYieldWithdrawal and getYieldOperation for the existing Aave/Morpho yield routes. Save the complete request and idempotency key before the first call. Amounts are USDC decimal units; withdrawals also accept 'all'. Select a supported marketId, or use chainId to select its Aave market.

const key = crypto.randomUUID();
const request = {
  marketId: 'aave-v3:11155111:usdc',
  amount: '10',
  autoRetry: {
    expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(),
    approval: { maxExecutionFeeWei: '200000000000000' },
    action: { maxExecutionFeeWei: '1000000000000000' },
  },
};
// Persist { key, request } before submitting; replay both unchanged.
const accepted = await para.createYieldDeposit(wallet.id, request, { idempotencyKey: key });
const operation = await para.getYieldOperation(wallet.id, accepted.id);
console.log(operation.status, operation.transactions, operation.autoRetry?.phases);

autoRetry is optional. Its required action ceiling authorizes one deposit/withdrawal transaction, including all same-nonce fee replacements. A needed approval requires its own explicit ceiling; Para never copies the action ceiling or rolls unused authorization forward. Include approval authorization on deposits unless existing allowance is known to cover the amount; withdrawals never need it. Each ceiling uses the existing per-transaction execution-gas semantics, excluding separate chain charges. This is not a cumulative operation budget.

A missing approval ceiling or a first transaction above its ceiling cannot be fixed by replaying the same request. Cancel the proven-unsigned operation first with POST /v1/wallets/{walletId}/yield/operations/{operationId}/cancel, then use a new key and adequate ceilings. The SDK does not wrap this cancellation route. Signed or uncertain phases remain tracked and cannot be replaced this way.

The original deadline applies across approval, action and every replay. If it expires after approval, the confirmed approval remains visible and the unsigned action does not run. autoRetry.phases exposes each phase's transaction ID, attempts and retry state. An initial reviewed transaction retains its exact bytes; replacements require a fresh policy allow and reuse that phase's principal accounting. Current policy and accounting must be re-proven; incompatible reuse, including unsupported approved spend overage, stops signing before MPC. A stopped retry can still be pending on-chain: track the same operation and transaction IDs until resolved. Omission retains existing exact-byte recovery.

Sponsored Transfers

Set sponsoredGas: true to require Para-paid gas on an eligible EVM token transfer. Para must enable your partner's existing admin-managed featureFlags.restGasSponsorship setting. If sponsorship cannot be honored, the request fails with an actionable error; it never falls back to wallet-paid gas. Accepted sponsored transfers return HTTP 202 with sponsoredGas: true, a transactionId, and a status. The response has no signedTransaction.

For a common transfer, provide the recipient, amount and asset (chain and token) with sponsoredGas: true; Para chooses the nonce, gas and fees. If you require an explicit nonce, fee cap or other control, the SDK forwards it unchanged. The current sponsorship API rejects unsupported controls with an actionable error. The SDK never drops those controls, flips sponsoredGas to false, or retries through wallet-paid execution.

Set sponsoredGas: false or omit it to require wallet-paid gas, regardless of partner sponsorship eligibility. Only sponsoredGas: true requests Para-paid gas. Transfer responses and transaction history/detail include sponsoredGas: true for Para-paid gas or false for wallet-paid gas. Older API versions may omit it; the deprecated response-only executionMode field remains for compatibility ('sponsored' or 'legacy'). Do not send executionMode in requests: the API rejects it with an actionable error.

The server must support sponsoredGas before you use it. The SDK expects wallet-paid gas when the field is omitted and leaves the request unchanged. If a server returns a different sponsorship choice, the SDK throws ParaRestSponsorshipError and preserves the result in error.response. For older responses, the SDK infers sponsorship from executionMode === 'sponsored'. A transaction may already have been submitted: reconcile its tracking ID/hash before retrying. This response check cannot undo a broadcast.

Upgrading an integration that previously relied on partner enrollment alone changes its gas-payment contract: send sponsoredGas: true on each new transfer that requires Para-paid gas. Omitted requests now require wallet-paid gas, and the SDK rejects an unexpected sponsored response, including an older response without the Boolean. Replaying an existing sponsored key requires sponsoredGas: true and unchanged economic intent. Replaying that key with false or an omitted flag returns 409 IDEMPOTENCY_CONFLICT without creating another transfer. Recover the original tracking ID or replay the original sponsored request; never retry the payout with a fresh idempotency key.

import { isSponsoredTransfer } from '@getpara/rest-sdk';

// Save this key with your payout before the request. Reuse it if the response is lost.
const requestKey = crypto.randomUUID();
const result = await para.transfer(
  wallet.id,
  { to: recipient, value: '1000000', chainId, tokenAddress, kind: 'ERC20', sponsoredGas: true },
  { idempotencyKey: requestKey },
);

if (isSponsoredTransfer(result)) {
  // Save the tracking ID, then poll this lookup from your background job.
  const record = await para.getRestTransaction(wallet.id, result.transactionId);
  console.log(record.status, record.stage, record.hash, record.actualGasCost);
} else {
  console.log(result.signedTransaction, result.txHash);
}

isSponsoredTransfer() narrows the response for tracking or reading signed bytes. It uses sponsoredGas when present and falls back to the older response-only executionMode field when the Boolean is absent.

Sponsored requests require an idempotency key and a positive integer value in token base units. Sponsorship manages nonce, gas, fees and submission. The only accepted body fields are to, value, chainId, tokenAddress, kind, broadcast, addressScreening, and sponsoredGas. All other fields are rejected, even when null or empty, including autoRetry, nonce/type/fee overrides, reviewId, initiatorUserId, and network. The generic TransferRequest still exposes initiator and network fields for legacy transfers. Omit kind or set it to 'ERC20'; omit broadcast and addressScreening or set them to true. Explicit sponsorship does not support Solana or disabled recipient screening. The server enforces partner eligibility and its supported chain/token configuration. Unavailable or paused sponsorship rejects explicit sponsored requests. The partner flag controls sponsorship eligibility; it never selects sponsorship for requests with sponsoredGas: false or an omitted field. Existing sponsored operations retain their tracking IDs.

Poll getRestTransaction until the status is confirmed, reverted, or failed; stop polling for policy approval when stage is requires_action. submitted with stage included is still awaiting receipt verification. submission_unknown means the provider may have received the operation. delivery_unverified means the operation was included but token delivery needs reconciliation; neither stage is a reason to send again. Reuse the original request and key to recover a lost response, and resume polling the original tracking ID. The SDK does not retry or poll automatically.

Conditional approval policies are not supported for sponsored transfers in this pilot. After an existing policy review is approved, explicitly continue the same operation:

await para.resumeRestTransaction(wallet.id, transactionId, { reviewId });

Resume accepts a review ID, not a new transfer. Repeating transfer with the original key only retrieves the existing operation and does not resume it. userOperationHash identifies the sponsored operation; txHash on the transfer response and hash in transaction history identify its on-chain transaction once included. actualGasCost is a decimal string in the chain's native-token base units after settlement, including included reverts. Keep it as a string or convert it to bigint, not number. Use getRestTransaction() for sponsorship stage, review details, user-operation hash, and settled gas cost; listRestTransactions() returns transaction summaries. Fetch persisted status to reconcile missed webhooks.

On-Ramp Purchases

getOnRampPurchase looks up a fiat on-ramp purchase by the id the SDK reports on the ONRAMP_LAUNCHED event — the same id that arrives as purchaseId on the onramp.completed webhook.

const purchase = await para.getOnRampPurchase(purchaseId);

if (purchase.status === 'FINISHED') {
  console.log(purchase.transactionHash, purchase.asset.symbol, purchase.assetQuantity);
}

The response is the onramp.completed payload plus a lifecycle status. A purchase that hasn't settled returns partial data — transactionHash and completedAt are null until status is FINISHED — so branch on status rather than inferring completion from the hash, and treat an unrecognized status as non-terminal.

As with transaction history, webhooks are the preferred way to learn about settlement; use this to backfill a missed delivery or to check a purchase still in flight. Check testMode before recording a purchase in production accounting: test purchases run a real quote but are never charged.

Webhook Types

Webhooks are delivered to your backend, so the types for handling them live here. ParaWebhookEvent is a discriminated union, so a switch on event.type narrows event.data to that event's payload:

import { WebhookEventType, type ParaWebhookEvent } from '@getpara/rest-sdk';

export function handleParaWebhook(event: ParaWebhookEvent) {
  switch (event.type) {
    case WebhookEventType.ONRAMP_COMPLETED:
      // event.data is OnrampCompletedPayload
      return credit(event.data.purchaseId, event.data.transactionHash);
    case WebhookEventType.REST_TRANSACTION_CONFIRMED:
      // event.data is RestTransactionConfirmedPayload
      return settle(event.data.transactionId, event.data.hash);
  }
}

Use ParaWebhookEvent rather than the generic WebhookEvent, whose type and data are uncorrelated — data stays the union of every payload regardless of type. Individual payload interfaces (OnrampCompletedPayload, WalletCreatedPayload, …), the WebhookEventPayloadMap, and ParaWebhookPayload<K> are exported too, for typing a single-event handler.

These are types only — this package does not receive, verify, or dispatch webhooks. Verify the signature on the raw request body before trusting a delivery; see the Para webhook docs for the procedure.

Adapters

import { createParaRestEthersSigner } from '@getpara/rest-sdk/ethers';
import { createParaRestViemAccount } from '@getpara/rest-sdk/viem';
import { createParaRestSolanaSigner } from '@getpara/rest-sdk/solana';

const ethersSigner = createParaRestEthersSigner({
  client: para,
  walletId,
  address,
});

const viemAccount = createParaRestViemAccount({
  client: para,
  walletId,
  address: address as `0x${string}`,
});

const solanaSigner = createParaRestSolanaSigner({
  client: para,
  walletId: solanaWalletId,
  address: solanaAddress,
});

Unsupported Surfaces

This package intentionally does not include browser connectors, React hooks, wagmi or RainbowKit connectors, mobile auth, user session flows, account creation UI, MPC share storage, share-migration encryption helpers, legacy adapters, or automatic retries. Use @getpara/server-sdk for migrateWalletShare. Keep API keys on your backend and expose only your own application-specific routes to clients.

Sandbox Probe Fixture

scripts/probe-signature-format.ts can probe sandbox signature formats when PARA_API_KEY is available. The sanitized fixture in src/__fixtures__/signature-formats.json documents observed behavior for regression tests. It is not a replacement for the OpenAPI contract or the live REST router behavior.