Navigation

Quickstart

The Paygo TypeScript client makes it easy to interact with the Paygo protocol. It handles transaction creation, signing, and submission, supporting both local private key signing and viem wallet integration.

Installation

Install the Paygo TypeScript client using bun:

bun add @witnessco/paygo-ts-client

Basic Usage

Here's a simple example showing how to set up the client, get test tokens, and make a transfer:

import {
	PaygoClient,
	FaucetRequest,
	Transfer,
	type TransactionResponse,
} from "@witnessco/paygo-ts-client";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";

// Private key for local signing
const localPk =
	"0x1476431d1020e741bdfb5af0e6f4d8a1d58d4f7b4de09d5053bfec86a20a7649";
// Private key for viem wallet
const viemPk =
	"0x6476431d1020e741bdfb5af0e6f4d8a1d58d4f7b4de09d5053bfec86a20a7649";

// Create a client with local signing
const localClient = new PaygoClient();
await localClient.setPk(localPk);

// Or create a client with viem wallet
const viemClient = new PaygoClient({
	viemWalletClient: createWalletClient({
		// sample account and transport
		account: privateKeyToAccount(viemPk),
		transport: http("https://eth.llamarpc.com"),
	}),
});

// Request tokens from faucet
const faucetRequest = new FaucetRequest(1000000000000000000n); // amount in cents
// Send the faucet request
const faucetResponse: TransactionResponse =
	await localClient.signAndPostTransactionFromParams(faucetRequest);
console.log("Faucet request sent, receipt:", faucetResponse.hash);

// Recipient address
const recipientAddress = "0x1A005245F091CEE6E5A6169eb76316F5451C842E";
// Create and send a transfer
const transfer = new Transfer(
	recipientAddress, // recipient address
	1000000000000000000n, // amount in cents
);
// Send the transfer
const transferResponse: TransactionResponse =
	await localClient.signAndPostTransactionFromParams(transfer);
console.log("Transfer sent, receipt:", transferResponse.hash);

Features

Accounts

Accounts in Paygo are similar to EVM-style addresses but with a simpler structure. Each account has an address, a nonce for transaction ordering, and a balance in cents. These accounts form the foundation of the Paygo system, allowing users to interact with it.

import { PaygoClient } from "@witnessco/paygo-ts-client";

// Create a client with local signing
// If private key is not set, client chooses random
const client = new PaygoClient();

// Get current address
const address = await client.address();
console.log(`Current address: ${address}`);

// Get account information
const account = await client.getAccount(address);
console.log(`Account balance: ${account.balance}`);
console.log(`Account nonce: ${account.nonce}`);

Delegations

Delegations provide a powerful way to manage shared funds and create payment authorization systems. They allow one account (the delegator) to grant spending authority to another account (the delegate), with controls for maximum spending amounts and expiration dates. This enables use cases like managing shared funds, creating payment authorization systems, and implementing multi-signature-like functionality.

import {
	PaygoClient,
	UpsertDelegation,
	DelegateTransfer,
	FaucetRequest,
	type TransactionResponse,
} from "@witnessco/paygo-ts-client";
import { hashMessage } from "viem";

// Private keys for alice and bob
const alicePk =
	"0x1476431d1020e741bdfb5af0e6f4d8a1d58d4f7b4de09d5053bfec86a20a7649";
const bobPk =
	"0x6476431d1020e741bdfb5af0e6f4d8a1d58d4f7b4de09d5053bfec86a20a7649";

// Create clients with local signing
const aliceClient = new PaygoClient();
await aliceClient.setPk(alicePk);
const bobClient = new PaygoClient();
await bobClient.setPk(bobPk);

// Request tokens from faucet to alice
const faucetRequest = new FaucetRequest(100000n); // amount in cents
// Send the faucet request
const faucetResponse: TransactionResponse =
	await aliceClient.signAndPostTransactionFromParams(faucetRequest);
console.log("Faucet request sent, receipt:", faucetResponse.hash);

// Client addresses
const aliceAddress = await aliceClient.address();
const bobAddress = await bobClient.address();
const charlieAddress = "0x1A005245F091CEE6E5A6169eb76316F5451C842E";

// Print balances before delegation
console.log(
	`Alice address: ${aliceAddress} and balance:`,
	await getBalance(aliceClient, aliceAddress),
);
console.log(
	`Bob address: ${bobAddress} and balance:`,
	await getBalance(bobClient, bobAddress),
);
console.log(
	`Charlie address: ${charlieAddress} and balance:`,
	await getBalance(aliceClient, charlieAddress),
);

// Create a delegation
const delegationId = hashMessage(crypto.randomUUID()); // 32 byte random hash
const delegation = new UpsertDelegation(
	delegationId,
	bobAddress, // delegate address
	1000000000000000000n, // allowance
	new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // expiration (30 days)
);
const response = await aliceClient.signAndPostTransactionFromParams(delegation);
console.log("Delegation created:", response.hash);

// Make a delegated transfer
const delegatedTransfer = new DelegateTransfer(
	delegationId,
	100n, // amount
	charlieAddress, // recipient
	aliceAddress, // sender
);
const transfer =
	await bobClient.signAndPostTransactionFromParams(delegatedTransfer);
console.log("Delegated transfer sent:", transfer.hash);

// Print balances after delegation
console.log(
	`Alice address: ${aliceAddress} and balance:`,
	await getBalance(aliceClient, aliceAddress),
);
console.log(
	`Bob address: ${bobAddress} and balance:`,
	await getBalance(bobClient, bobAddress),
);
console.log(
	`Charlie address: ${charlieAddress} and balance:`,
	await getBalance(aliceClient, charlieAddress),
);

// Simple function to get balance of user
async function getBalance(client: PaygoClient, address: string) {
	const account = await client.getAccount(address as `0x${string}`);
	return account.balance;
}

Escrows

Escrows in Paygo provide a flexible way to handle conditional payments and time-locked funds. They allow you to lock funds with specific conditions that can be satisfied with an SP1 zk proof, making them ideal for multi-party agreements and conditional payments. Each escrow has a unique ID, locked amount, expiration date, and optional fulfiller address and proof requirements. Funds can be released when conditions are met or when the escrow expires.

import {
	PaygoClient,
	CreateEscrow,
	FulfillEscrow,
	ReleaseEscrow,
	type TransactionResponse,
	FaucetRequest,
} from "@witnessco/paygo-ts-client";
import { hashMessage } from "viem";

// Sample private keys for alice and bob
const alicePk =
	"0x1476431d1020e741bdfb5af0e6f4d8a1d58d4f7b4de09d5053bfec86a20a7649";
const bobPk =
	"0x6476431d1020e741bdfb5af0e6f4d8a1d58d4f7b4de09d5053bfec86a20a7649";

// Create clients with local signing
const aliceClient = new PaygoClient();
await aliceClient.setPk(alicePk);
const bobClient = new PaygoClient();
await bobClient.setPk(bobPk);

const aliceAddress = await aliceClient.address();
const bobAddress = await bobClient.address();

// Request tokens from faucet to alice
const faucetRequest = new FaucetRequest(100000n); // amount in cents
// Send the faucet request
const faucetResponse: TransactionResponse =
	await aliceClient.signAndPostTransactionFromParams(faucetRequest);
console.log("Faucet request sent, receipt:", faucetResponse.hash);

console.log(
	"Alice balance before creating first escrow:",
	await getBalance(aliceClient, aliceAddress),
);
console.log(
	"Bob balance before creating first escrow:",
	await getBalance(bobClient, bobAddress),
);

// Create an escrow
let escrowId = hashMessage(crypto.randomUUID()); // 32 byte random hash
let createEscrow = new CreateEscrow(
	escrowId,
	100n, // amount
	new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // expiration (30 days)
	bobAddress, // optional fulfiller address
	"0x0000000000000000000000000000000000000000000000000000000000000000", // optional vkey of an sp1 circuit
);
const response =
	await aliceClient.signAndPostTransactionFromParams(createEscrow);
console.log("Escrow created:", response.hash);

console.log(
	"Alice balance after creating first escrow:",
	await getBalance(aliceClient, aliceAddress),
);
console.log(
	"Bob balance after creating first escrow:",
	await getBalance(bobClient, bobAddress),
);

// Fulfill an escrow
const fulfillEscrow = new FulfillEscrow(
	escrowId,
	"0x0000000000000000000000000000000000000000000000000000000000000000", // proof
);
const fulfillment =
	await bobClient.signAndPostTransactionFromParams(fulfillEscrow);
console.log("Escrow fulfilled:", fulfillment.hash);

console.log(
	"Alice balance after fulfilling first escrow:",
	await getBalance(aliceClient, aliceAddress),
);
console.log(
	"Bob balance after fulfilling first escrow:",
	await getBalance(bobClient, bobAddress),
);

// Create a new escrow
escrowId = hashMessage(crypto.randomUUID());
createEscrow = new CreateEscrow(
	escrowId,
	100n, // amount
	new Date(Date.now() + 1 * 1000), // expiration (1 seconds)
	bobAddress, // optional fulfiller address
	"0x0000000000000000000000000000000000000000000000000000000000000000", // optional vkey of an sp1 circuit
);
const response2 =
	await aliceClient.signAndPostTransactionFromParams(createEscrow);
console.log("Escrow created:", response2.hash);

console.log(
	"Alice balance after creating second escrow:",
	await getBalance(aliceClient, aliceAddress),
);
console.log(
	"Bob balance after creating second escrow:",
	await getBalance(bobClient, bobAddress),
);

// Wait for the escrow to expire
await new Promise((resolve) => setTimeout(resolve, 2000));

// Release an expired escrow
const releaseEscrow = new ReleaseEscrow(escrowId);
const release =
	await aliceClient.signAndPostTransactionFromParams(releaseEscrow);
console.log("Escrow released:", release.hash);

console.log(
	"Alice balance after releasing second escrow:",
	await getBalance(aliceClient, aliceAddress),
);
console.log(
	"Bob balance after releasing second escrow:",
	await getBalance(bobClient, bobAddress),
);

// Simple function to get balance of user
async function getBalance(client: PaygoClient, address: string) {
	const account = await client.getAccount(address as `0x${string}`);
	return account.balance;
}

Transaction History

Paygo maintains a complete and verifiable history of all transactions. You can query transactions by their hash, the signer's address, or the block number they were included in. This comprehensive history allows for easy tracking and verification of all operations on the network, ensuring transparency and auditability.

import {
	PaygoClient,
	type ProcessedTransaction,
} from "@witnessco/paygo-ts-client";

const client = new PaygoClient();
// Sample transaction hash
const txHash =
	"0x8310e0897e56084d7780b6c12a8943814b4355ad441ae7fd02f1abc541b9eaa0";

// Get transaction by hash
const tx: ProcessedTransaction = await client.getTransactionByHash(txHash);
console.log("Transaction:", tx);

// Get transactions by signer
const address = await client.address();
const txs = await client.getTransactionsBySigner(address);
console.log(`Found ${txs.length} transactions by signer ${address}`);

// Get transactions in a block
const blockTxs = await client.getTransactionsByBlock(1n);
console.log(`Found ${blockTxs.length} transactions in block 1`);

Viem Integration

The Paygo client supports integration with viem, a TypeScript interface for Ethereum that provides a consistent API for interacting with Ethereum nodes. This allows you to use Paygo with any viem-compatible wallet or provider.

import { PaygoClient } from "@witnessco/paygo-ts-client";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";

// Sample private key
const pk = "0x6476431d1020e741bdfb5af0e6f4d8a1d58d4f7b4de09d5053bfec86a20a7649";

// Create a viem wallet client
const viemWalletClient = createWalletClient({
	// sample account and transport
	account: privateKeyToAccount(pk),
	transport: http("https://eth.llamarpc.com"),
});

// Create a Paygo client with viem
const client = new PaygoClient({
	viemWalletClient,
});

// Use the client as normal
const address = await client.address();
console.log(`Connected address: ${address}`);

Switching Between Signing Methods

The client allows you to switch between different signing methods (local private key and viem wallet) based on your needs.

import {
	PaygoClient,
	SignerConfig,
	FaucetRequest,
} from "@witnessco/paygo-ts-client";
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";

const localPk =
	"0x1476431d1020e741bdfb5af0e6f4d8a1d58d4f7b4de09d5053bfec86a20a7649";
const viemPk =
	"0x6476431d1020e741bdfb5af0e6f4d8a1d58d4f7b4de09d5053bfec86a20a7649";

// Start with local signing
const client = new PaygoClient();
await client.setPk(localPk);

// Create a viem wallet client
const viemWalletClient = createWalletClient({
	// sample account and transport
	account: privateKeyToAccount(viemPk),
	transport: http("https://eth.llamarpc.com"),
});

// Setup viem wallet client in paygo client
await client.setViemWalletClient(viemWalletClient);

// Request tokens from faucet to viem signer's address
const faucetRequestViem = new FaucetRequest(1000000000000000000n); // amount in cents
const faucetResponseViem = await client.signAndPostTransactionFromParams(
	faucetRequestViem,
	SignerConfig.Viem,
);
console.log("Faucet request sent:", faucetResponseViem.hash);

// Request tokens from faucet to local signer's address
const faucetRequestLocal = new FaucetRequest(1000000000000000000n); // amount in cents
const faucetResponseLocal = await client.signAndPostTransactionFromParams(
	faucetRequestLocal,
	SignerConfig.Local,
);
console.log("Faucet request sent:", faucetResponseLocal.hash);