Skip to main content

SDK Reference

Package: payid

Subpath exports: payid/client ยท payid/server ยท payid/sessionPolicy ยท payid/context ยท payid/rule


Factory Functionsโ€‹

createPayIDClient(params?) โ€” Browser / Edgeโ€‹

Safe for browsers, mobile apps, and edge runtimes. Signer is injected per-call in evaluateAndProve.

import { createPayIDClient } from "payid/client";
// or from root:
import { createPayIDClient } from "payid";

createPayIDClient(params?: {
wasm?: Uint8Array; // custom WASM binary (optional)
debugTrace?: boolean; // log evaluation trace
}): PayIDClient

createPayID is an alias for createPayIDClient kept for backwards compatibility.


createPayIDServer(params) โ€” Backend / Bundlerโ€‹

For servers, relayers, and bundlers. Signer is bound at construction. Never ship to browser.

import { createPayIDServer } from "payid/server";
// or from root:
import { createPayIDServer } from "payid";

createPayIDServer(params: {
signer: ethers.Signer; // server wallet โ€” signs Decision Proofs
trustedIssuers?: Set<string>; // addresses allowed to sign Context V2 attestations
debugTrace?: boolean;
wasm?: Uint8Array;
}): PayIDServer
ParamTypeDescription
signerethers.SignerServer wallet that signs Decision Proofs
trustedIssuersSet<string>?Issuer addresses for Context V2 attestation verification
debugTraceboolean?Log evaluation trace to console

client.ready() โ€” Client onlyโ€‹

Wait for the WASM rule engine to finish loading. Only needed in Node.js or test environments. In React, usePayIDFlow handles this lazily โ€” no manual ready() needed.

const client = createPayIDClient({});
await client.ready(); // only needed in Node.js / scripts
note

PayIDServer does not have a ready() method โ€” the server-side engine is always ready.


payid.evaluate(context, rule)โ€‹

Pure rule evaluation โ€” no signing, no network calls.

async evaluate(
context: RuleContext,
rule: RuleConfig | RuleSource
): Promise<RuleResult>
const result = await payid.evaluate(
{
tx: { sender: "0x...", receiver: "0x...", asset: "USDC", amount: "150000000", chainId: 1 },
env: { timestamp: Math.floor(Date.now() / 1000) },
},
{
version: "1",
logic: "AND",
rules: [
{ id: "min_amount", if: { field: "tx.amount", op: ">=", value: "100000000" } },
],
}
);

console.log(result.decision); // "ALLOW" | "REJECT"

payid.evaluateAndProve(params)โ€‹

Evaluate rules and generate an EIP-712 Decision Proof. The payer signs with their own wallet. Returns proof: null if the decision is REJECT.

async evaluateAndProve(params: {
// Rule evaluation
context: RuleContext; // payment details
authorityRule: RuleConfig; // merchant's rules (loaded from IPFS)
evaluationRule?: RuleConfig; // override rule for evaluation only

// Session policy (Channel A โ€” optional)
sessionPolicyV2?: SessionPolicyV2;

// Payment identity
payId: string; // e.g. "pay.id/merchant"
payer: string; // payer wallet address
receiver: string; // receiver wallet address
asset: string; // token address (zero address = native token: ETH, MATIC, A0GI, etc.)
amount: bigint; // amount in token's smallest unit

// Signing
signer: ethers.Signer; // payer's ethers signer

// On-chain binding
verifyingContract: string; // PayIDVerifier address
ruleAuthority: string; // CombinedRuleStorage address
ruleSetHashOverride?: string; // pass activeRuleSetHash from chain to avoid mismatch

// Timing
chainId: number; // e.g. 31337 for localhost
blockTimestamp: number; // Math.floor(Date.now() / 1000)
ttlSeconds?: number; // proof TTL, default 300 seconds
}): Promise<{ result: RuleResult; proof: DecisionProof | null }>
blockTimestamp is required

Always pass blockTimestamp: Math.floor(Date.now() / 1000). This is used for proof expiry calculation and must match the chain's block time as closely as possible.

Example:

import { createPayIDClient } from "payid/client";
import { ethers } from "ethers";

const payid = createPayIDClient({});
await payid.ready(); // only needed in Node.js

const { result, proof } = await payid.evaluateAndProve({
context: {
tx: {
sender: payerAddress,
receiver: merchantAddress,
asset: usdcAddress,
amount: "150000000",
chainId: 31337,
},
env: { timestamp: Math.floor(Date.now() / 1000) },
state: { spentToday: "0", period: new Date().toISOString().slice(0, 10) },
},
authorityRule: {
version: "1",
logic: "AND",
rules: ruleConfigs, // loaded from IPFS
},
payId: "pay.id/merchant",
payer: payerAddress,
receiver: merchantAddress,
asset: usdcAddress,
amount: 150_000_000n,
signer: payerSigner,
verifyingContract: PAYID_VERIFIER,
ruleAuthority: COMBINED_RULE_STORAGE,
ruleSetHashOverride: activeHashFromChain, // prevents hash mismatch
chainId: 31337,
blockTimestamp: Math.floor(Date.now() / 1000),
ttlSeconds: 300,
});

if (!proof) throw new Error(`Rejected: ${result.reason ?? result.code}`);

// Submit to blockchain
await payContract.payERC20(proof.payload, proof.signature, []);

buildContextV2(params)โ€‹

Build Context V2 with server-signed attestations. Server mode only.

import { buildContextV2 } from "payid/context";

const contextV2 = await buildContextV2({
baseContext: {
tx: { sender: "0x...", receiver: "0x...", asset: "USDC", amount: "50000000", chainId: 1 },
},
env: { issuer: envSigner },
state: { issuer: stateSigner, spentToday: "0", period: "DAY" },
oracle: { issuer: oracleSigner, data: { country: "ID", kycLevel: "2" } },
risk: { issuer: riskSigner, score: 25, category: "LOW", modelHash: "0x..." },
});

Each field gets a proof attestation signed by the respective issuer. The SDK verifies all proofs against trustedIssuers during evaluation.


createSessionPolicyV2(params)โ€‹

Create a Channel A session policy โ€” signed by the receiver with their payment constraints. Used to generate QR codes that payers scan.

import { createSessionPolicyV2 } from "payid/sessionPolicy";

const policy = await createSessionPolicyV2({
receiver: merchantAddress,
ruleSetHash: activeRuleSetHash,
ruleAuthority: COMBINED_RULE_STORAGE,
allowedAsset: usdcAddress,
maxAmount: 50_000_000n, // 50 USDC
expiresAt: Math.floor(Date.now() / 1000) + 3600, // 1 hour
payId: "pay.id/merchant",
chainId: 31337,
verifyingContract: PAYID_VERIFIER,
signer: merchantSigner,
});

encodeSessionPolicyV2QR(policy) / decodeSessionPolicyV2QR(str)โ€‹

Encode/decode a SessionPolicyV2 to/from a QR-safe string ("payid-v2:<base64url>").

import { encodeSessionPolicyV2QR, decodeSessionPolicyV2QR } from "payid/sessionPolicy";

// Merchant side โ€” encode to QR payload
const qrString = encodeSessionPolicyV2QR(policy);
// โ†’ "payid-v2:eyJ2ZXJzaW9uIjoicGF5aWQuc2..."

// Payer side โ€” decode from scanned QR
const policy = decodeSessionPolicyV2QR(qrString);

Typesโ€‹

RuleContextโ€‹

interface RuleContext {
tx: {
sender?: string; // payer wallet address
receiver?: string; // merchant wallet address
asset: string; // token address or symbol
amount: string; // amount in token units (as string)
chainId: number;
};
payId?: { id: string; owner: string };
env?: { timestamp: number };
state?: { spentToday?: string; period?: string; [key: string]: unknown };
}

RuleConfigโ€‹

interface RuleConfig {
version?: string;
logic: "AND" | "OR";
rules: AnyRule[];
requires?: string[]; // ["oracle", "risk", "state"] for server fields
message?: string;
}
type AnyRule = SimpleRule | MultiConditionRule | NestedRule;

RuleResultโ€‹

interface RuleResult {
decision: "ALLOW" | "REJECT";
code: string;
reason?: string;
}

DecisionProofโ€‹

interface DecisionProof {
payload: {
version: string; // keccak256("2")
payId: string;
payer: string;
receiver: string;
asset: string;
amount: bigint;
contextHash: string;
ruleSetHash: string; // must match merchant's active hash on-chain
ruleAuthority: string; // CombinedRuleStorage address
issuedAt: bigint;
expiresAt: bigint; // issuedAt + ttlSeconds
nonce: string; // random bytes32 โ€” prevents replay
requiresAttestation: boolean;
attestationUIDsHash: string; // keccak256(abi.encode(attestationUIDs)), ZeroHash if none
};
signature: string; // EIP-712 signature from payer's wallet
}

SessionPolicyV2โ€‹

interface SessionPolicyV2 {
version: "payid.session.policy.v2";
receiver: string;
ruleSetHash: string;
ruleAuthority: string;
allowedAsset: string;
maxAmount: string; // bigint serialized as string
expiresAt: number;
policyNonce: string;
payId: string;
chainId: number;
verifyingContract: string;
signature: string; // EIP-712 from receiver
}

Result Codesโ€‹

CodeCondition
OKAll rules passed
RULE_FAILEDRule condition evaluated to false
FIELD_NOT_FOUNDContext field referenced in rule is missing
INVALID_CONFIGRule JSON is malformed
CONTEXT_OR_ENGINE_ERRORWASM / runtime error
INVALID_ENGINE_OUTPUTWASM output format unexpected