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

privily-sdk

v1.1.0

Published

Privily's SDK (node/web compat)

Readme

Privily SDK

A TypeScript SDK for building on Privily — a multi-chain, privacy-first payment layer. Supports EVM, Solana, Starknet, Tron, and Bitcoin ecosystems. Post-quantum account support is planned (see the "Falcon512 — Coming Soon" section).

Table of Contents


Features

Module 1 — Bridge / Anonymity / Swap ✅ Production

  • Current Account Standard — STRK-based v0 account flow is supported today across all integrations.
  • Multi-chain — EVM (Ethereum, Polygon, BSC…), Solana, Starknet, Tron, and Bitcoin (Legacy + Taproot).
  • Cross-chain Bridge — Deposit, withdraw, and transfer assets across networks.
  • Privacy by Design — Bridge/transfer/payment flows are private by default on Privily.
  • Anonymity Flags — Optional isAnon flags further restrict counterparty metadata visibility in specific histories.
  • Asset Swap — Supported on eligible non-Privily chains (depends on liquidity provider availability).
  • Disperse — One-to-many transfers in a single transaction.
  • Delayed Withdrawals — Schedule withdrawals for future execution.
  • Payment Requests — Request funds from users (directed or open), and pay requests on-chain cross-chain.
  • History & Tracking — Full operation, deposit, transfer, and withdrawal history with pagination.
  • TypeScript Native — Full type safety, IntelliSense, and TSDoc throughout.
  • Dual Module — Ships as both ESM and CommonJS.

Module 2 — Payment Provider (Beta)

⚠️ Not currently available. Payment-provider contracts are not deployed yet (including revenue sharing). APIs may exist in the SDK, but these features should be treated as upcoming and not usable in production or testing environments yet.

  • On-chain Product Commerce — Coming soon.
  • Subscription Management — Coming soon.
  • Revenue Sharing — Coming soon.

Installation

npm install privily-sdk

⚠️ Self-custody notice: You are fully responsible for storing and backing up your keys/seed material. Privily never has access to your private keys at any time and cannot recover lost credentials or funds.


Network Endpoints

  • RPC Wrapper URL (RPCWrapper / RPCWrapperV0): https://rpc.privily.fi
    • Use for keygen message, deployment, auth, and Privily RPC wrapper operations.
  • Units RPC URL (UnitsProvider / UnitsAccount): https://privily.karnot.xyz
    • Use for all Units/on-chain execution (provider, account calls, and transactions).

Quick Start

1. Generate v0 STRK Keys

Use an unauthenticated wrapper to fetch the public keygen message, sign it with the user wallet, then pass that signature as additional entropy during v0 key derivation:

import { RPCWrapper, getAccountV0SkFromEntropy, getStarkPub } from 'privily-sdk';

// 1) Public route: wrapper without credentials
const rpc = new RPCWrapper();
const msgToSign = await rpc.getKeygenMessage();

// 2) Sign with wallet SDK (ethers / solana wallet adapter / starknet.js / etc.)
const walletSignature = await wallet.signMessage(msgToSign);

// 3) Derive v0 stark key material using the wallet signature as extra entropy
const starkPrivateKey = getAccountV0SkFromEntropy(msgToSign, walletSignature);
const starkPublicKey = getStarkPub(starkPrivateKey);

See Account Key Generation for chain-specific accountV0Keys* helpers.

2. Deploy a Privily Account

import { RPCWrapper, PRIVILY_PLATFORM_ID } from 'privily-sdk';

const rpc = new RPCWrapper();

const result = await rpc.deployEvmUserAccount({
  signer: evmProvider,           // EVM wallet (must implement sign())
  pk: starkPublicKey,            // v0 Stark public key (hex string)
  platform: PRIVILY_PLATFORM_ID, // Optional, defaults to Privily
  version: 0,                    // v0 flow
});
console.log('Deployed:', result);

See Account Deployment for all chains.

3. Authenticate & Query Balances

All authenticated v0 RPC operations go through RPCWrapperV0, which manages JWT login automatically:

import { RPCWrapperV0, PRIVILY_PLATFORM_ID } from 'privily-sdk';

const rpc = new RPCWrapperV0(starkPrivateKey, PRIVILY_PLATFORM_ID);

const balances = await rpc.getBalances();
console.log('Balances:', balances);

const prices = await rpc.getAssetsPrices();
console.log('Prices:', prices);

4. Send Transactions

Use UnitsAccount to execute on-chain transactions with a STRK-based account:

import { UnitsProvider, UnitsAccount, retrieveAccountV0, PRIVILY_PLATFORM_ID } from 'privily-sdk';

const provider = new UnitsProvider('https://privily.karnot.xyz');
const address = retrieveAccountV0(starkPublicKey, PRIVILY_PLATFORM_ID);
const account = new UnitsAccount(provider, address, starkPrivateKey);

const { transaction_hash } = await account.sendTransaction([
  {
    contractAddress: tokenAddress,
    entrypoint: 'transfer',
    calldata: [recipientAddress, amountLow, amountHigh],
  },
]);

const receipt = await account.waitForTransaction(transaction_hash);
console.log('TX status:', receipt.execution_status);

Alternative: You can also generate a STRK private key directly with starknet.js (see Alternative: Direct Starknet.js Key Generation).


Module 1 — Bridge / Anonymity / Swap

Account Key Generation

Generate v0 (STRK) keys from chain-specific signatures. Each function accepts a chain provider/signer, the platform keygen message, and optional extra entropy.

v0 STRK Keys

| Function | Chain | Returns | |---|---|---| | accountV0KeysEvm(evmProvider, msg, extraEntropy?) | EVM | Promise<string> (STRK private key) | | accountV0KeysSvm(svmProvider, msg, extraEntropy?) | Solana | Promise<string> | | accountV0KeysSnvm(signer, msg, extraEntropy?) | Starknet | Promise<string> | | accountV0KeysBtc(btcProvider, msg, extraEntropy?) | Bitcoin | Promise<string> | | accountV0KeysTvm(tronWebTrx, msg, extraEntropy?) | Tron | Promise<string> |

v0 key note: these STRK keys are Privily-generated from the keygen flow (accountV0Keys*). If you already have a wallet signature from ethers/solana/starknet wallets, pass the raw signature string as the third extraEntropy parameter for compatibility with wallet import/recovery in the dapp.

Alternative: Direct Starknet.js Key Generation

import { stark } from 'starknet';
import { getStarkPub } from 'privily-sdk';

const starkPrivateKey = stark.randomAddress();
const starkPublicKey = getStarkPub(starkPrivateKey);

This direct flow can be imported into the dapp and used, but the wallet-link between the original wallet and the Privily account is not created, so wallet-linked recovery/import experiences may be limited.


Account Deployment

Smart account deployment is performed through the RPCWrapper instance. All deploy methods accept a single params object.

import { RPCWrapper } from 'privily-sdk';

const rpc = new RPCWrapper(); // public routes + deployment helpers

| Method on rpc | Chain | Params type | Returns | |---|---|---|---| | rpc.deployEvmUserAccount(params) | EVM | DeployEVMAccountParams | Promise<StringResponse> | | rpc.deploySvmUserAccount(params) | Solana | DeploySVMAccountParams | Promise<StringResponse> | | rpc.deploySnvmUserAccount(params) | Starknet | DeploySNVMAccountParams | Promise<StringResponse> | | rpc.deployLegacyBtcAccount(params) | BTC Legacy | DeployBTCAccountParams | Promise<StringResponse> | | rpc.deployTaprootBtcAccount(params) | BTC Taproot | DeployBTCAccountParams | Promise<StringResponse> | | rpc.deployTvmUserAccount(params) | Tron | DeployTVMAccountParams | Promise<StringResponse> |

All params types extend DeployAccountParamsBase:

type DeployAccountParamsBase = {
  pk: string | Uint8Array;   // v0 Stark public key: hex string (preferred) or Uint8Array
  platform?: string;         // Platform ID (default: Privily platform)
  nonce?: number;            // Account nonce (default: 0)
  version?: number;          // 0 = v0/STRK (default: 0)
  refCode?: string;          // Optional referral code
  specialCode?: string;      // Optional special code (e.g. fee exemption)
};

Starknet-specific note: DeploySNVMAccountParams additionally requires accountDeployCalldata — use getSnvmAccountDeployData(provider, snvmAddress) to compute it.

import { getSnvmAccountDeployData } from 'privily-sdk';

const accountDeployCalldata = await getSnvmAccountDeployData(starknetProvider, snvmAddress);

await rpc.deploySnvmUserAccount({
  signer: starknetSigner,
  pk: starkPublicKey,
  accountDeployCalldata,
});

Account Retrieval

Compute Privily account addresses deterministically from a public key without deploying:

import { retrieveAccountV0, retrieveAccount, AccountVersion } from 'privily-sdk';

// v0 STRK account address
const addrV0 = retrieveAccountV0(starkPublicKey, platformId, index);

// Version-agnostic helper
const addrAny = retrieveAccount(pk, platformId, index, AccountVersion.v0);

RPC Wrapper

RPCWrapper and RPCWrapperV0 handle Privily RPC operations. JWT login is automatic — wrappers refresh tokens as needed.

import { RPCWrapper, RPCWrapperV0 } from 'privily-sdk';

// Public/deployment wrapper (first arg is undefined because no key credentials are needed for public routes)
const rpc = new RPCWrapper(undefined, platformId, index, onLogin?);

// STRK-based auth (v0 accounts)
const rpcV0 = new RPCWrapperV0(starkPrivateKey, platformId, index, onLogin?);

The optional onLogin callback is invoked after every successful authentication with the new JWT token and its expiry timestamp, allowing you to persist the session.

Auth State

| Method | Description | |---|---| | rpc.isLoggedIn() | Returns true if the current token is valid (with 2-min expiry buffer) | | rpc.hasAccount() | Returns true if account credentials are set | | rpc.setCredentials(token, expiry) | Manually inject a pre-obtained token (skips login) | | rpc.setSk(sk) | Update the Stark private key (RPCWrapperV0 only) |

Available Methods

| Method | Auth | Description | |---|---|---| | Public (no auth) | | | | getKeygenMessage(platform?) | ❌ | Get the platform keygen message | | getAssets() | ❌ | Get supported assets configuration | | getAssetsPrices(assets?) | ❌ | Get current market prices for assets | | getLiquidityProviderConf() | ❌ | Get liquidity provider configuration | | getBetaData(account) | ❌ | Get beta/waitlist status for an account | | joinWaitlist(refCode) | ❌ | Request to join the beta waitlist | | getPublicPaymentRequest(id) | ❌ | Get a public (open) payment request | | publicPayRequest(params) | ❌ | Pay a public request without authentication | | getPublicOperationStatus(id) | ❌ | Get operation status for non-authenticated operations | | Account | | | | getBalances(assets?) | ✅ | Get account balances on Privily | | getOnchainBalances() | ✅ | Get on-chain balances across networks | | getGasTokenBalance() | ✅ | Get gas token balances (own + executor) | | claimGasToken() | ✅ | Claim gas tokens for transaction fees | | Bridge / Swap | | | | getBridgeData(params) | ✅ | Get bridge transaction data for cross-chain transfers | | getDisperseBridgeData(params) | ✅ | Get bridge data for multi-recipient (disperse) transfers | | commitBridgeQuotes(quoteIds) | ✅ | Commit previously fetched bridge quotes (up to 20) | | processTransfer(params) | ✅ | Submit a transfer transaction for execution | | processDelayedWithdrawals(params) | ✅ | Submit one or more delayed withdrawal transactions | | fullWithdrawal(asset, to) | ✅ | Withdraw full balance of an asset to an address | | History & Status | | | | getOperationStatus(id) | ✅ | Get operation status by ID | | getOperationHistory(side?, limit?, startKey?, altStartKey?) | ✅ | Paginated all-operation history | | getDepositHistory(limit?, startKey?) | ✅ | Paginated deposit history | | getTransferHistory(side?, limit?, startKey?) | ✅ | Paginated transfer history | | getWithdrawalHistory(limit?, startKey?) | ✅ | Paginated withdrawal history | | Saved Addresses | | | | getWithdrawalAddresses(searchBy?, search?, limit?, startKey?) | ✅ | List saved withdrawal addresses | | addWithdrawalAddress(address, label) | ✅ | Save a withdrawal address | | updateWithdrawalAddress(id, address, label) | ✅ | Update a saved address | | deleteWithdrawalAddress(id) | ✅ | Delete a saved address | | Payment Requests | | | | requestPayment(params) | ✅ | Create a payment request | | getPaymentRequest(id) | ✅ | Get a payment request by ID | | getSentPaymentRequests(limit?, startKey?) | ✅ | List sent payment requests | | getReceivedPaymentRequests(limit?, startKey?) | ✅ | List received payment requests | | cancelPaymentRequest(id) | ✅ | Cancel a payment request | | Referrals | | | | initRefCode() | ✅ | Initialize a referral code | | setReferer(referer) | ✅ | Link a referrer (by code string or { address, index? }) | | deleteReferer() | ✅ | Remove referrer link | | getRefData() | ✅ | Get referral data and earnings | | getReferees(limit?, startKey?) | ✅ | List referred users | | getTradingRebates() | ✅ | Get trading rebates per asset |

Swap support note: isExchange routes depend on liquidity-provider availability and are not supported for PRIVILY -> PRIVILY pairs.

Bridge Data Params

getBridgeData and getDisperseBridgeData accept structured params objects:

// Standard bridge (one recipient)
await rpc.getBridgeData({
  inChain: 'ethereum',                   // Source chain
  outChain: 'PRIVILY',                   // Destination chain
  inAsset: '0x...ethAsset',             // Source asset address
  outAsset: '0x...privilyAsset',        // Destination asset address
  amount: '1000000',                     // Amount (in smallest unit, e.g. 6-decimal USDC)
  from: '0x...depositorAddress',         // Source address
  to: { address: '0x...recipient' },     // Recipient (omit to send to self)
  refuel: false,                          // Gas refuel on destination
  execTimestamp: undefined,               // number — Unix timestamp (seconds) for delayed execution; omit for immediate
  requestId: 'req_...',                   // Optional linked payment request
  conf: { isAnon: false },               // Optional operation flags (`isAnon`, `isSend`, `isExchange`)
});

// Disperse (one-to-many)
await rpc.getDisperseBridgeData({
  inChain: 'ethereum',
  inAsset: '0x...ethAsset',
  from: '0x...depositor',
  disperseData: [
    { outChain: 'PRIVILY', outAsset: '0x...a', amount: '500000', to: { address: '0x...r1' } },
    { outChain: 'PRIVILY', outAsset: '0x...b', amount: '500000', to: { address: '0x...r2' } },
  ],
});

getBridgeData, getDisperseBridgeData, and publicPayRequest return a NewBridge object containing all required execution payloads (bridgeTx, optional delayedBridgeTx, IDs, provider metadata). This same shape is used for deposit, withdrawal, disperse, swap, and payment-request flows.

Anonymity semantics: Privily bridge/transfer/payment operations are private by design. conf.isAnon/requestPayment.isAnon adds extra counterparty metadata masking in specific recipient/request history views; it does not toggle core privacy on/off.

Executing bridgeTx by Chain Type

bridge.bridgeTx is chain-specific:

  • EVM (EVMBridgeTxData = EvmTx[]): execute each tx with wallet/provider using to, data, and optional value.
  • Privily / Starknet (SNVMBridgeTxData = Call[]): pass calls directly to UnitsAccount or Starknet account execute.
  • Solana (SVMBridgeTxData = { transactionBase64: string }): decode serialized transaction payload, set fresh blockhash/fee payer as required by wallet flow, sign, then send.
  • Tron (TVMBridgeTxData = TvmTx[]): use returned Tron call parameters (contractAddress, functionSelector, parametes, optional value) to build and broadcast via TronWeb.
// EVM payload
for (const tx of bridge.bridgeTx as EvmTx[]) {
  await evmWallet.sendTransaction({
    to: tx.to,
    data: tx.data,
    value: tx.value ?? undefined,
  });
}

// Privily / Starknet payload
await unitsOrSnAccount.execute(bridge.bridgeTx as Call[]);

// Solana payload (SVMBridgeTxData)
const svmPayload = bridge.bridgeTx as { transactionBase64: string };
const txBytes = Buffer.from(svmPayload.transactionBase64, 'base64');
const tx = solanaWeb3.VersionedTransaction.deserialize(txBytes);
tx.message.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
tx.sign([payerKeypair]);
await connection.sendTransaction(tx);

// Tron payload
for (const tx of bridge.bridgeTx as TvmTx[]) {
  const res = await tronWeb.transactionBuilder.triggerSmartContract(
    tx.contractAddress,
    tx.functionSelector,
    {},
    [tx.parametes],
    tronWeb.address.toHex(sender),
  );
  const signed = await tronWeb.trx.sign(res.transaction);
  await tronWeb.trx.sendRawTransaction(signed);
}

Payment Request Params

requestPayment creates a payment request and returns its ID. The person who receives the request fulfills it by passing that ID as requestId to getBridgeData.

// --- Create a directed request (ask a specific user to pay you) ---
const requestId = await rpc.requestPayment({
  to: { address: '0x...payerPrivilyAddress' },  // The Privily address of the person you are asking to pay
  amount: '5000000',                              // 5 USDC (6 decimals)
  asset: TOKENS.USDC.address,
  memo: 'Invoice #42',                           // Optional message
  isAnon: false,                                 // Set true to hide requester identity from the payer's request view
});
console.log('Request created:', requestId);

// --- Create an open (public) request — anyone can pay it ---
const openRequestId = await rpc.requestPayment({
  to: undefined,           // No specific payer → open request payable by anyone
  amount: '1000000',
  asset: TOKENS.USDC.address,
  memo: 'Tip jar',
});

// --- Retrieve a request (authenticated — must be sender or payer) ---
const req = await rpc.getPaymentRequest(requestId);
console.log('Status:', PAYMENT_REQUEST_STATUS_STR[req.status]);

// --- Retrieve an open request without auth ---
const publicReq = await rpc.getPublicPaymentRequest(openRequestId);

// --- Cancel a request you sent ---
await rpc.cancelPaymentRequest(requestId);

publicPayRequest pays an open request without authentication. It returns the same NewBridge shape as getBridgeData; execute bridgeTx exactly as described in Executing bridgeTx by Chain Type:

// Pay a public request from an external EVM chain (no auth required)
const bridge = await rpc.publicPayRequest({
  inChain: 'ethereum',               // Source chain of the payer
  requestId: openRequestId,          // The open payment request ID
  from: '0x...payerEvmAddress',     // Optional: payer's address on the source chain
});

// Execute bridge.bridgeTx on Ethereum with the payer's wallet
console.log('Payment TX:', bridge.bridgeTx);

Chain Address Utilities

Convert between blockchain addresses and the Privily ChainAddress format (a { low: bigint, high: bigint } pair that packs the address and chain discriminator into a 256-bit value):

import {
  hexAddressToChainAddress,
  bs58AddressToChainAddress,
  addressToChainAddress,
  chainAddressToU256,
  getAddressDiscriminator,
  ChainAddressDiscriminator,
} from 'privily-sdk';

// From EVM hex address + explicit discriminator
const chainAddr = hexAddressToChainAddress('0x1234...', ChainAddressDiscriminator.EVM);

// From Solana base58 address + explicit discriminator
const svmAddr = bs58AddressToChainAddress('AaBb...', ChainAddressDiscriminator.SVM);

// Auto-detect chain from address format
const auto = addressToChainAddress('0x1234...');  // works with EVM, SNVM, SVM, BTC, TVM

// Convert to u256 for contract calls
const u256 = chainAddressToU256(chainAddr);

// Detect discriminator only
const disc = getAddressDiscriminator('0xAbCd...');  // ChainAddressDiscriminator.EVM

Discriminators:

| Discriminator | Value | Chains | |---|---|---| | ChainAddressDiscriminator.EVM | "0x0" | Ethereum, Polygon, BSC, … | | ChainAddressDiscriminator.SVM | "0x1" | Solana | | ChainAddressDiscriminator.SNVM | "0x2" | Starknet | | ChainAddressDiscriminator.BTC | "0x3" | Bitcoin (legacy + taproot) | | ChainAddressDiscriminator.TVM | "0x4" | Tron |


Units Network

The Units network is Privily's app-chain. All Privily account operations (transfers, contract calls, program deployments) are executed on Units via these classes.

UnitsProvider

Low-level JSON-RPC client for the Units network:

import { UnitsProvider } from 'privily-sdk';

const provider = new UnitsProvider('https://privily.karnot.xyz');

const { chain_id } = await provider.getChainId();
const { nonce } = await provider.getNonce(accountAddress);

UnitsAccount

On-chain account class for sending transactions, deploying programs, and reading state. Use UnitsAccount with a STRK private key for on-chain execution today:

import { UnitsAccount, UnitsProvider } from 'privily-sdk';

// STRK-based account (current recommended approach)
const account = new UnitsAccount(provider, address, privateKey);

// Send one or multiple calls
const { transaction_hash } = await account.sendTransaction([call1, call2]);
// or equivalently:
const { transaction_hash: txHash } = await account.execute(singleCall);

// Wait for confirmation
const receipt = await account.waitForTransaction(transaction_hash);
// receipt.execution_status: { type: "SUCCEEDED" } | { type: "REVERTED", error: string }

// Read contract state
const { result } = await account.call(contractAddress, 'get_balance', [userAddress]);

// Batch read
const results = await account.multiCall([
  { contractAddress, entrypoint: 'balance_of', calldata: [addr1] },
  { contractAddress, entrypoint: 'balance_of', calldata: [addr2] },
]);

// Declare a program (class)
await account.declareProgram(programArtifact, compiledHash, 'PUBLIC');

// Deploy a program instance via UDC
const { transaction_hash: deployTx, program_address } =
  await account.deployProgram(classHash, constructorArgs, salt);

// Deploy this account on-chain (for undeployed accounts created with newUndeployedAccount)
await account.deploySelf();

// SNIP-9 outside execution (meta-transactions / off-chain signing)
const outsideCall = await account.getOutsideExecutionCall(calls, callerAddress);

Account Factory Helpers

import { deployBaseAccount, getUnitsAccount } from 'privily-sdk';

// Deploy a new basic (STRK) account
const { privateKey, unitsAccount } = await deployBaseAccount(provider, sk);

// Load an existing account (no deployment)
const acc = getUnitsAccount(provider, address, privateKey);

Starknet Utilities

Helper functions for Starknet data types and contract interactions:

import {
  getProvider, getAccount,
  u256ArrayToBn, bnToU256Array,
  strToCairoStr, cairoStrToStr,
  genStarkPrivate, getStarkPub,
  getContract, declareContract, deployAccount,
  waitForTransaction,
} from 'privily-sdk';

// Create a Starknet RPC provider / account
const provider = getProvider('https://your-starknet-rpc');
const account = getAccount(provider, accountAddress, privateKey);

// Data conversions
const value = u256ArrayToBn([lowFelt, highFelt]);     // BigInt from u256 [low, high]
const [low, high] = bnToU256Array(1_000_000n);         // u256 [low, high] from BigInt

// Cairo string encoding
const cairoStr = strToCairoStr('hello');               // BigNumberish[]
const original = cairoStrToStr(serializedArray);       // string

// Key utilities
const sk = genStarkPrivate();                          // Random Stark private key
const pk = getStarkPub(sk);                            // Corresponding Stark public key

Falcon512 — Coming Soon

🔬 Planned — not yet available.

Privily's post-quantum roadmap includes native Falcon512 on-chain account support. The following features are planned for a future release and are not yet available:

  • FalconUnitsAccount — On-chain account that signs transactions directly with Falcon512 keys (no STRK key required).
  • deployFalconUnitsAccount / getFalconUnitsAccount — Factory helpers for Falcon-based accounts.
  • getFalconAccount — Starknet utility for loading a Falcon account.
  • Native Starknet Falcon account contract — A Starknet smart-account contract secured by Falcon512 signatures, enabling a fully post-quantum Starknet identity.

For on-chain transactions today, use UnitsAccount with a STRK private key. See Quick Start and Recipe 2.


Constants & Assets

import {
  PRIVILY_PLATFORM_ID,       // Default Privily platform identifier
  PRIVILY_CHAIN_ID,          // Privily app-chain ID
  ANY_CALLER,                // Sentinel address for SNIP-9 (any caller allowed)
  TOKENS,                    // { ETH, STRK, USDC, USDT } — decimals, address, wrapper, l1Address, min
  WRAPPERS,                  // token address → wrapper contract address
  WRAPPERS_TO_TOKEN,         // wrapper address → token symbol
  OPERATION_TYPE,            // { DEPOSIT: 0, WITHDRAWAL: 1, TRANSFER: 2 }
  OPERATION_TYPE_STR,        // { 0: "Deposit", 1: "Withdrawal", 2: "Transfer" }
  OPERATION_STATUS,          // { PENDING: 0, EXECUTING_APPCHAIN: 1, SUCCESS: 2, FAILED: 3 }
  OPERATION_STATUS_STR,      // { 0: "Pending", … }
  PAYMENT_REQUEST_STATUS,    // { PENDING: 0, EXECUTED: 1, FAILED: 2, CANCELLED: 3 }
  PAYMENT_REQUEST_STATUS_STR,
  OPERATION_FLAG,            // Bitmask flags: SEND, PAY, EXCHANGE, ANON_FROM, ANON_TO, REFUEL, DISPERSE
  SIDE,                      // { FROM: 0, TO: 1 }
} from 'privily-sdk';

Module 2 — Payment Provider (Beta)

⚠️ Not available yet. These features are not deployed on-chain at this time (including revenue sharing). Treat this section as roadmap/API preview only.

⚠️ No liability disclaimer: If you attempt unsupported integrations before deployment readiness, you assume all risk. Privily is not responsible for losses or side effects from unsupported usage.

Revenue Sharing

Not implemented yet. Contracts are not deployed.


Product Commerce (Beta)

Not implemented yet. Contracts are not deployed.


Subscription Management (Beta)

Not implemented yet. Contracts are not deployed.


Recipes & Examples

The examples below cover the most common real-world integration patterns. All examples use the EVM chain for key generation, but the same patterns apply for Solana, Starknet, Bitcoin, and Tron by substituting the relevant accountV0Keys* and deploy* functions.


Recipe 1: Full Setup (EVM wallet → Privily keys → deploy)

import {
  RPCWrapper, RPCWrapperV0,
  getAccountV0SkFromEntropy, getStarkPub,
  PRIVILY_PLATFORM_ID,
} from 'privily-sdk';

async function fullSetup(evmProvider: EvmSigner) {
  const rpc = new RPCWrapper();

  // 1. Fetch the platform keygen message (public, no auth required)
  const msg = await rpc.getKeygenMessage();

  // 2. Sign with wallet SDK and use signature as extra entropy for v0 keygen
  const walletSignature = (await evmProvider.sign(msg)).signature;
  const starkPrivateKey = getAccountV0SkFromEntropy(msg, walletSignature);
  const starkPublicKey = getStarkPub(starkPrivateKey);

  // Persist Stark private key securely
  console.log('starkPrivateKey:', starkPrivateKey);
  console.log('starkPublicKey:', starkPublicKey);

  // 3. Deploy Privily account (only needed once per wallet)
  await rpc.deployEvmUserAccount({
    signer: evmProvider,
    pk: starkPublicKey,
    platform: PRIVILY_PLATFORM_ID,
    version: 0,
  });

  // 4. Authenticate subsequent calls using RPCWrapperV0
  const rpcV0 = new RPCWrapperV0(starkPrivateKey, PRIVILY_PLATFORM_ID);

  const balances = await rpcV0.getBalances();
  console.log('Balances:', balances);

  return { rpc, rpcV0, starkPrivateKey, starkPublicKey };
}

Recipe 2: Restore Existing Keys from Storage

If you have already deployed a Privily account and saved your keys, reconstruct the wrapper and account directly — no keygen or deployment needed.

import {
  RPCWrapperV0,
  UnitsProvider, UnitsAccount,
  retrieveAccountV0,
  getStarkPub,
  PRIVILY_PLATFORM_ID,
} from 'privily-sdk';

// --- v0 (STRK) account — on-chain transactions ---

const provider = new UnitsProvider('https://privily.karnot.xyz');

const starkPrivateKey = '0x...'; // previously saved private key
const starkPublicKey = getStarkPub(starkPrivateKey); // derive public key from private key
const addrV0 = retrieveAccountV0(starkPublicKey, PRIVILY_PLATFORM_ID);

const rpcV0 = new RPCWrapperV0(starkPrivateKey, PRIVILY_PLATFORM_ID);
const accountV0 = new UnitsAccount(provider, addrV0, starkPrivateKey);

Tip: If you use a session callback (onLogin) you can also restore a previously saved JWT token to skip the login round-trip:

rpcV0.setCredentials(savedToken, savedExpiry); // expiry in ms

Bridge Execution Helper (All getBridgeData-Style Responses)

The same execution pattern applies to:

  • getBridgeData
  • getDisperseBridgeData
  • publicPayRequest
type BridgePayload = EvmTx[] | Call[] | { transactionBase64: string } | TvmTx[];

async function executeBridgeTxByChain(bridgeTx: BridgePayload, sourceChain: string) {
  if (sourceChain === 'PRIVILY' || sourceChain === 'STARKNET') {
    // bridgeTx is Call[] → execute with UnitsAccount or Starknet Account
    await unitsOrSnAccount.execute(bridgeTx);
    return;
  }

  if (sourceChain === 'solana') {
    // bridgeTx is { transactionBase64 } in SDK types
    const payload = bridgeTx as { transactionBase64: string };
    const tx = solanaWeb3.VersionedTransaction.deserialize(
      Buffer.from(payload.transactionBase64, 'base64')
    );
    tx.message.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
    tx.sign([payerKeypair]);
    await connection.sendTransaction(tx);
    return;
  }

  if (sourceChain === 'tron') {
    // bridgeTx is TvmTx[]
    for (const tx of bridgeTx as TvmTx[]) {
      const built = await tronWeb.transactionBuilder.triggerSmartContract(
        tx.contractAddress,
        tx.functionSelector,
        {},
        [tx.parametes],
        tronWeb.address.toHex(senderAddress),
      );
      const signed = await tronWeb.trx.sign(built.transaction);
      await tronWeb.trx.sendRawTransaction(signed);
    }
    return;
  }

  // EVM-like chains: bridgeTx is EvmTx[]
  for (const tx of bridgeTx as EvmTx[]) {
    await evmWallet.sendTransaction({
      to: tx.to,
      data: tx.data,
      value: tx.value ?? undefined,
    });
  }
}

Recipe 3: Bridge (EVM → Privily)

Deposit assets from an external EVM chain into your Privily balance.

import { RPCWrapper, OPERATION_STATUS, OPERATION_STATUS_STR } from 'privily-sdk';

async function bridgeIn(rpc: RPCWrapper, evmDepositorAddress: string) {
  // 1. Fetch bridge transaction data
  const bridge = await rpc.getBridgeData({
    inChain: 'ethereum',
    outChain: 'PRIVILY',
    inAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
    outAsset: TOKENS.USDC.address,                         // USDC on Privily
    amount: '1000000',                                      // 1 USDC (6 decimals)
    from: evmDepositorAddress,
    // 'to' is omitted → funds go to your own Privily account
  });

  // 2. Execute the returned source-chain payload
  await executeBridgeTxByChain(bridge.bridgeTx, 'ethereum');

  // 3. Poll operation status on Privily
  await pollOperationStatus(rpc, bridge.id);
}

async function pollOperationStatus(rpc: RPCWrapper, operationId: string) {
  for (let i = 0; i < 60; i++) {
    const { status } = await rpc.getOperationStatus(operationId);
    console.log('Status:', OPERATION_STATUS_STR[status]);

    if (status === OPERATION_STATUS.SUCCESS) {
      console.log('✅ Bridge complete');
      return;
    }
    if (status === OPERATION_STATUS.FAILED) {
      throw new Error('❌ Bridge failed');
    }

    await new Promise(r => setTimeout(r, 5000)); // wait 5s
  }
  throw new Error('Timed out waiting for operation');
}

Recipe 4: Bridge (Privily → EVM withdrawal)

Withdraw assets from your Privily balance back to an external chain.

async function bridgeOut(rpc: RPCWrapper, evmRecipient: string) {
  const bridge = await rpc.getBridgeData({
    inChain: 'PRIVILY',
    outChain: 'ethereum',
    inAsset: TOKENS.USDC.address,
    outAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
    amount: '1000000',
    to: { address: evmRecipient },
  });

  // bridge.bridgeTx is a Privily app-chain transaction — submit via processTransfer
  const opId = await rpc.processTransfer({ tx: bridge.bridgeTx });

  console.log('Withdrawal submitted, operation ID:', opId);
  await pollOperationStatus(rpc, opId);
}

Recipe 5: Bridge — Delayed Withdrawal

Schedule a withdrawal to execute at a specific future Unix timestamp. Useful for vesting, escrow, or recurring payments.

async function delayedWithdrawal(rpc: RPCWrapper, evmRecipient: string) {
  // Schedule execution 24 hours from now
  const execTimestamp = Math.floor(Date.now() / 1000) + 86400;

  const bridge = await rpc.getBridgeData({
    inChain: 'PRIVILY',
    outChain: 'ethereum',
    inAsset: TOKENS.USDC.address,
    outAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
    amount: '1000000',
    to: { address: evmRecipient },
    execTimestamp,              // Tells the bridge to schedule rather than execute immediately
  });

  // bridge.delayedBridgeTx contains the on-chain calls to schedule
  // bridge.bridgeTx is the tx to approve/lock funds now
  await rpc.processTransfer({ tx: bridge.bridgeTx, execTimestamp });

  // Later, when execTimestamp is reached, submit the delayed tx:
  if (bridge.delayedBridgeTx) {
    await rpc.processDelayedWithdrawals(
      bridge.delayedBridgeTx.map((tx: any) => ({ tx, execTimestamp }))
    );
    console.log('Delayed withdrawal submitted');
  }
}

Recipe 6: Bridge — Disperse (One-to-Many)

Send a single inbound amount and split it across multiple recipients in one operation.

async function disperseToMany(rpc: RPCWrapper, ethDepositor: string) {
  const bridge = await rpc.getDisperseBridgeData({
    inChain: 'ethereum',
    inAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
    from: ethDepositor,
    disperseData: [
      {
        outChain: 'PRIVILY',
        outAsset: TOKENS.USDC.address,
        amount: '500000',                    // 0.5 USDC to recipient 1
        to: { address: '0x...recipient1' },
      },
      {
        outChain: 'PRIVILY',
        outAsset: TOKENS.USDC.address,
        amount: '500000',                    // 0.5 USDC to recipient 2
        to: { address: '0x...recipient2' },
      },
      {
        outChain: 'solana',
        outAsset: '...usdcOnSolana',
        amount: '1000000',                   // 1 USDC to a Solana recipient
        to: { address: 'SolAnaRecipientBase58...' },
      },
    ],
  });

  await executeBridgeTxByChain(bridge.bridgeTx, 'ethereum');
  // Track all sub-operations via bridge.id / bridge.altId
}

Recipe 7: Swap (Eligible Non-Privily Chains)

Swap is not supported when both legs are on PRIVILY. Use supported external chains (EVM/SNVM/SVM/etc.), and note liquidity-provider routing can make some swaps unavailable at runtime.

async function swapAssets(rpc: RPCWrapper) {
  // Example: swap from Ethereum USDC to Starknet USDC (non-PRIVILY swap path)
  const bridge = await rpc.getBridgeData({
    inChain: 'ethereum',
    outChain: 'STARKNET',
    inAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // Ethereum USDC
    outAsset: '0x...starknetUsdc',
    amount: '1000000',              // 1 USDC
    from: '0x...payerEvmAddress',
    conf: { isExchange: true },     // Flag this as an exchange operation
  });

  await executeBridgeTxByChain(bridge.bridgeTx, 'ethereum');
  console.log('Swap source transaction(s) submitted');
}

Recipe 8: Anonymous Transfer

All Privily transfers are private by default. isAnon is an additional history-visibility control for counterparty metadata in recipient/request contexts (it is not a global on/off privacy switch).

async function anonTransfer(rpc: RPCWrapper) {
  const bridge = await rpc.getBridgeData({
    inChain: 'PRIVILY',
    outChain: 'PRIVILY',
    inAsset: TOKENS.USDC.address,
    outAsset: TOKENS.USDC.address,
    amount: '1000000',
    to: { address: '0x...recipientPrivilyAddress' },
    conf: { isAnon: true },   // Extra metadata masking in recipient/request history views
  });

  const opId = await rpc.processTransfer({
    tx: bridge.bridgeTx,
    conf: { isAnon: true },
  });

  console.log('Anonymous transfer submitted:', opId);
}

Recipe 9: Paginate Operation History

Fetch and iterate through your full operation history using cursor-based pagination.

import { RPCWrapper, OPERATION_STATUS_STR, OPERATION_TYPE_STR, SIDE } from 'privily-sdk';

async function loadHistory(rpc: RPCWrapper) {
  let startKey: Record<string, any> | undefined;
  const allOps = [];

  do {
    const page = await rpc.getOperationHistory(
      null,     // side: null = all, SIDE.FROM = sent, SIDE.TO = received
      20,       // page size
      startKey, // cursor from previous page (undefined for first page)
    );

    for (const op of page.items) {
      console.log(
        `[${OPERATION_TYPE_STR[op.operationType]}]`,
        `Status: ${OPERATION_STATUS_STR[op.status]}`,
        `Amount: ${op.inAmount}`,
        `Chain: ${op.inChain} → ${op.outChain ?? op.inChain}`,
        op.isSend ? '(sent)' : '(received)',
        (op.isAnonFrom || op.isAnonTo) ? '(anon-flagged)' : '',
      );
    }

    allOps.push(...page.items);
    startKey = page.lastKey; // undefined when last page reached
  } while (startKey);

  console.log(`Total operations: ${allOps.length}`);
}

Equivalent helpers for typed history:

// Deposits only
const deposits = await rpc.getDepositHistory(20, startKey);

// Transfers (sent/received)
const sentTxs = await rpc.getTransferHistory(SIDE.FROM, 20, startKey);
const receivedTxs = await rpc.getTransferHistory(SIDE.TO, 20, startKey);

// Withdrawals only
const withdrawals = await rpc.getWithdrawalHistory(20, startKey);

Recipe 10: Manage Withdrawal Addresses

Save and manage trusted addresses for fast withdrawals.

async function manageAddresses(rpc: RPCWrapper) {
  // Add a new address
  await rpc.addWithdrawalAddress(
    '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', // EVM address
    'My Metamask (Ethereum)'
  );

  // List all saved addresses (paginated)
  const { items } = await rpc.getWithdrawalAddresses(
    undefined,  // searchBy field (undefined = no filter)
    undefined,  // search term
    20,         // limit
  );
  console.log('Saved addresses:', items);

  // Search by label
  const { items: filtered } = await rpc.getWithdrawalAddresses('label', 'Metamask');

  // Update a saved address
  await rpc.updateWithdrawalAddress(
    items[0].id,
    '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
    'My Metamask (Ethereum) — updated'
  );

  // Delete an address
  await rpc.deleteWithdrawalAddress(items[0].id);
}

Recipe 11: Referral Program

async function referralFlow(rpc: RPCWrapper) {
  // Initialize your referral code (do once)
  const { refCode } = await rpc.initRefCode();
  console.log('Your referral code:', refCode);

  // Link a referrer (do once, before first transaction)
  await rpc.setReferer('FRIEND123');  // by ref code string

  // Get your referral stats
  const refData = await rpc.getRefData();
  console.log('Referral data:', refData);

  // List users you referred (paginated)
  const { items: referees } = await rpc.getReferees(20);
  console.log('Referees:', referees);

  // Get your trading rebates per asset
  const rebates = await rpc.getTradingRebates();
  console.log('Rebates:', rebates);
}

Recipe 12: Create a Payment Request

Request funds from another Privily user (directed) or from anyone (open/public). The recipient pays by calling getBridgeData with the returned requestId.

import { RPCWrapper, TOKENS, PAYMENT_REQUEST_STATUS, PAYMENT_REQUEST_STATUS_STR } from 'privily-sdk';

async function createPaymentRequests(rpc: RPCWrapper) {
  // --- Directed request: ask a specific Privily user to pay you ---
  const requestId = await rpc.requestPayment({
    to: { address: '0x...payerPrivilyAddress' },  // Privily account of the person you are billing
    amount: '5000000',                              // 5 USDC (6 decimals)
    asset: TOKENS.USDC.address,
    memo: 'Invoice #42 — freelance work',
    isAnon: false,                                 // Set true to hide requester metadata from the payer's request view
  });
  console.log('Directed request ID:', requestId);

  // --- Open (public) request: anyone can pay it ---
  const openRequestId = await rpc.requestPayment({
    to: undefined,          // No specific payer → open request
    amount: '1000000',      // 1 USDC
    asset: TOKENS.USDC.address,
    memo: 'Tip jar ☕',
  });
  console.log('Open request ID:', openRequestId);

  // --- Check status of a sent request ---
  const req = await rpc.getPaymentRequest(requestId);
  console.log('Status:', PAYMENT_REQUEST_STATUS_STR[req.status]);
  // PENDING → EXECUTED once paid, CANCELLED if you cancel

  // --- List all requests you have sent (paginated) ---
  const { items: sent } = await rpc.getSentPaymentRequests(20);
  for (const r of sent) {
    console.log(r.id, PAYMENT_REQUEST_STATUS_STR[r.status], r.amount, r.memo);
  }

  // --- List all requests you have received (paginated) ---
  const { items: received } = await rpc.getReceivedPaymentRequests(20);
  for (const r of received) {
    console.log(r.id, PAYMENT_REQUEST_STATUS_STR[r.status]);
  }

  // --- Cancel a pending request ---
  await rpc.cancelPaymentRequest(requestId);
  console.log('Request cancelled');
}

Recipe 13: Pay a Received Payment Request

When you receive a payment request, settle it by passing its ID as requestId to getBridgeData. The bridge fields (outChain, outAsset, amount, to) are automatically resolved from the request — you only need to supply the source-chain details.

import { RPCWrapper, TOKENS, PAYMENT_REQUEST_STATUS } from 'privily-sdk';

async function payReceivedRequest(rpc: RPCWrapper, requestId: string) {
  // First, inspect the request (optional — to confirm amount / asset)
  const req = await rpc.getPaymentRequest(requestId);

  if (req.status !== PAYMENT_REQUEST_STATUS.PENDING) {
    throw new Error(`Request is not payable (status: ${req.status})`);
  }

  // Get bridge transaction data — pass requestId to auto-fill outChain/outAsset/amount/to
  const bridge = await rpc.getBridgeData({
    inChain: 'PRIVILY',   // Paying from within your Privily balance
    requestId,            // Auto-fills destination details from the request
  });

  // Submit the bridge transaction from Privily
  const opId = await rpc.processTransfer({ tx: bridge.bridgeTx });
  console.log('Payment submitted, operation ID:', opId);

  // Or if paying from an external chain (e.g. EVM depositor):
  const bridgeFromEvm = await rpc.getBridgeData({
    inChain: 'ethereum',
    inAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
    from: '0x...yourEvmAddress',
    requestId,  // Fills outChain/outAsset/amount/to from the request
  });
  await executeBridgeTxByChain(bridgeFromEvm.bridgeTx, 'ethereum');
}

Recipe 14: Pay a Public (Open) Payment Request Without Auth

Open payment requests can be paid by anyone — including users who don't have a Privily account. Use getPublicPaymentRequest to inspect and publicPayRequest to obtain the bridge transaction.

import { RPCWrapper } from 'privily-sdk';

async function payPublicRequest(rpc: RPCWrapper, openRequestId: string) {
  // Inspect the request — no auth required
  const req = await rpc.getPublicPaymentRequest(openRequestId);
  console.log('Paying:', req.amount, 'of asset', req.asset, '— memo:', req.memo);

  // Get the bridge transaction — source chain and depositor address are all that's needed
  const bridge = await rpc.publicPayRequest({
    inChain: 'ethereum',                // Source chain of the payer
    requestId: openRequestId,           // The open request to fulfill
    from: '0x...payerEvmAddress',      // Payer's address on the source chain (optional)
  });

  // Execute on the payer's source chain
  await executeBridgeTxByChain(bridge.bridgeTx, 'ethereum');

  // After submission, anyone can track the operation:
  const status = await rpc.getPublicOperationStatus(bridge.id);
  console.log('Operation status code:', status);
}

Contributing

git clone https://github.com/PrivilyFi/sdk.git
cd sdk
npm install
npm run build

License

MIT — see LICENSE for details.