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

@spectratools/tx-shared

v0.6.2

Published

Shared transaction primitives, signer types, and chain config for spectra tools

Readme

@spectratools/tx-shared

Shared transaction primitives for Spectra tools:

  • signer resolution (resolveSigner)
  • transaction lifecycle execution (executeTx)
  • shared signer CLI/env parsing helpers (toSignerOptions)
  • structured transaction errors (TxError)
  • Abstract chain helpers (abstractMainnet, createAbstractClient)

This package is designed for consuming CLIs (for example @spectratools/assembly-cli) so write-capable commands follow the same signer precedence, dry-run behavior, and error handling.

Install

pnpm add @spectratools/tx-shared

Signer providers

resolveSigner() uses deterministic precedence:

  1. privateKey
  2. keystorePath (+ keystorePassword)
  3. Privy (privy flag and/or PRIVY_* env)

If no provider is configured, it throws TxError with code SIGNER_NOT_CONFIGURED.

Provider setup

1) Private key

export PRIVATE_KEY="0x<64-hex-chars>"

Or pass privateKey directly in code.

2) Keystore

# CLI convention
--keystore /path/to/keystore.json --password "$KEYSTORE_PASSWORD"

# env fallback for password
export KEYSTORE_PASSWORD="..."

resolveSigner() requires a password when keystorePath is provided.

3) Privy

Required env:

export PRIVY_APP_ID="..."
export PRIVY_WALLET_ID="..."
export PRIVY_AUTHORIZATION_KEY="wallet-auth:<base64-pkcs8-p256-private-key>"

Optional env:

export PRIVY_API_URL="https://api.sandbox.privy.io"

Optional CLI flags (from signerFlagSchema):

--privy
--privy-api-url "https://api.sandbox.privy.io"

Notes:

  • --privy is optional when PRIVY_APP_ID + PRIVY_WALLET_ID + PRIVY_AUTHORIZATION_KEY are present; resolveSigner() auto-detects Privy config.
  • --privy-api-url / PRIVY_API_URL override the default https://api.privy.io.
  • Privy account methods are mapped as:
    • sendTransactioneth_sendTransaction
    • signMessagepersonal_sign
    • signTypedDataeth_signTypedData_v4
    • signTransactioneth_signTransaction (returns serialized tx hex; broadcast separately if needed)

resolveSigner() usage

Direct options

import { resolveSigner } from '@spectratools/tx-shared';

const signer = await resolveSigner({
  privateKey: process.env.PRIVATE_KEY,
});

console.log(signer.provider); // 'private-key' | 'keystore' | 'privy'
console.log(signer.address); // 0x...

From shared CLI flags + env

import {
  resolveSigner,
  signerEnvSchema,
  signerFlagSchema,
  toSignerOptions,
} from '@spectratools/tx-shared';

const flags = signerFlagSchema.parse({
  privy: true,
  'privy-api-url': process.env.PRIVY_API_URL,
});

const env = signerEnvSchema.parse(process.env);
const signer = await resolveSigner(toSignerOptions(flags, env));

executeTx() lifecycle

executeTx() performs this flow:

  1. estimate gas (estimateContractGas)
  2. simulate (simulateContract)
  3. preflight Privy policies (when signer is Privy-backed)
  4. submit (writeContract) unless dryRun: true
  5. wait for receipt (waitForTransactionReceipt)
  6. normalize result into a shared output shape

Privy transaction flow example (dry-run + live)

import { http, createPublicClient, createWalletClient, parseAbi } from 'viem';
import {
  abstractMainnet,
  executeTx,
  resolveSigner,
  signerEnvSchema,
  signerFlagSchema,
  toSignerOptions,
} from '@spectratools/tx-shared';

const flags = signerFlagSchema.parse({
  privy: true,
  'privy-api-url': process.env.PRIVY_API_URL,
});
const env = signerEnvSchema.parse(process.env);
const signer = await resolveSigner(toSignerOptions(flags, env));

const publicClient = createPublicClient({
  chain: abstractMainnet,
  transport: http(process.env.ABSTRACT_RPC_URL),
});
const walletClient = createWalletClient({
  account: signer.account,
  chain: abstractMainnet,
  transport: http(process.env.ABSTRACT_RPC_URL),
});

const registryAbi = parseAbi(['function register() payable']);

const dryRun = await executeTx({
  publicClient,
  walletClient,
  account: signer.account,
  address: '0x1111111111111111111111111111111111111111',
  abi: registryAbi,
  functionName: 'register',
  value: 1000000000000000n,
  dryRun: true,
});

if (dryRun.status === 'dry-run') {
  console.log(dryRun.estimatedGas.toString());
  console.log(dryRun.privyPolicy?.status); // 'allowed' | 'blocked'
}

const live = await executeTx({
  publicClient,
  walletClient,
  account: signer.account,
  address: '0x1111111111111111111111111111111111111111',
  abi: registryAbi,
  functionName: 'register',
  value: 1000000000000000n,
});

console.log(live.hash, live.status);

Privy signing flow example

const signer = await resolveSigner({
  privy: true,
  privyAppId: process.env.PRIVY_APP_ID,
  privyWalletId: process.env.PRIVY_WALLET_ID,
  privyAuthorizationKey: process.env.PRIVY_AUTHORIZATION_KEY,
  privyApiUrl: process.env.PRIVY_API_URL,
});

if (signer.provider !== 'privy') throw new Error('Expected Privy signer');

const messageSig = await signer.account.signMessage({ message: 'hello from tx-shared' });

const typedDataSig = await signer.account.signTypedData({
  domain: { name: 'SpectraTools', version: '1', chainId: 2741 },
  primaryType: 'Ping',
  types: {
    Ping: [{ name: 'message', type: 'string' }],
  },
  message: { message: 'privy typed data' },
});

const signedTxHex = await signer.account.signTransaction({
  to: '0x1111111111111111111111111111111111111111',
  chainId: 2741,
  nonce: 1,
  gas: 21000n,
  maxFeePerGas: 1000000000n,
  maxPriorityFeePerGas: 1000000000n,
  value: 0n,
  data: '0x',
});

console.log(messageSig, typedDataSig, signedTxHex);

Structured errors

executeTx() and signer helpers throw TxError with stable code values:

  • INSUFFICIENT_FUNDS
  • NONCE_CONFLICT
  • GAS_ESTIMATION_FAILED
  • TX_REVERTED
  • SIGNER_NOT_CONFIGURED
  • KEYSTORE_DECRYPT_FAILED
  • PRIVY_AUTH_FAILED
  • PRIVY_TRANSPORT_FAILED
  • PRIVY_POLICY_BLOCKED
import { TxError } from '@spectratools/tx-shared';

try {
  // resolveSigner(...) + executeTx(...)
} catch (error) {
  if (error instanceof TxError) {
    switch (error.code) {
      case 'INSUFFICIENT_FUNDS':
      case 'NONCE_CONFLICT':
      case 'GAS_ESTIMATION_FAILED':
      case 'TX_REVERTED':
      case 'SIGNER_NOT_CONFIGURED':
      case 'KEYSTORE_DECRYPT_FAILED':
      case 'PRIVY_AUTH_FAILED':
      case 'PRIVY_TRANSPORT_FAILED':
      case 'PRIVY_POLICY_BLOCKED':
        throw error;
    }
  }

  throw error;
}

Troubleshooting

  • SIGNER_NOT_CONFIGURED
    • check signer input precedence
    • confirm at least one provider is configured
  • KEYSTORE_DECRYPT_FAILED
    • verify file path and password
    • ensure keystore is valid V3 JSON
  • PRIVY_AUTH_FAILED
    • verify required env: PRIVY_APP_ID, PRIVY_WALLET_ID, PRIVY_AUTHORIZATION_KEY
    • validate PRIVY_AUTHORIZATION_KEY format: wallet-auth:<base64-pkcs8-p256-private-key>
    • verify PRIVY_API_URL override points at a valid Privy API host
  • PRIVY_TRANSPORT_FAILED
    • check Privy API availability and credentials
    • inspect upstream intent failure payloads for status + reason
  • PRIVY_POLICY_BLOCKED
    • review active wallet policy allowlists and native value caps
    • use dryRun: true and inspect result.privyPolicy before broadcasting
  • GAS_ESTIMATION_FAILED / TX_REVERTED
    • validate function args and value
    • run with dryRun: true first
  • NONCE_CONFLICT
    • refresh nonce and retry once with an explicit override if needed

Consumer integration examples