# Get Token Balance
Source: https://docs.chipipay.com/sdk/api/endpoint/get-token-balance
GET /chipi-wallets/token-balance
Get the balance of a specific token for a wallet
```bash cURL theme={null}
curl -X GET "https://api.chipipay.com/v1/chipi-wallets/token-balance?externalUserId=user_123&chainToken=USDC&chain=STARKNET" \
-H "Authorization: Bearer sk_prod_xxxx" \
-H "x-api-key: pk_xxxx"
```
```typescript TypeScript theme={null}
const params = new URLSearchParams({
externalUserId: "user_123",
chainToken: "USDC",
chain: "STARKNET"
});
const response = await fetch(
`https://api.chipipay.com/v1/chipi-wallets/token-balance?${params}`,
{
headers: {
"Authorization": "Bearer sk_prod_xxxx",
"x-api-key": "pk_xxxx",
}
}
);
const balance = await response.json();
// balance.balance is a raw integer string — use BigInt for precision
const raw = BigInt(balance.balance);
const scale = 10n ** BigInt(balance.decimals);
const whole = raw / scale;
const fraction = (raw % scale).toString().padStart(balance.decimals, "0").replace(/0+$/, "");
const humanReadable = fraction ? `${whole}.${fraction}` : `${whole}`;
```
```python Python theme={null}
import requests
from decimal import Decimal
response = requests.get(
"https://api.chipipay.com/v1/chipi-wallets/token-balance",
params={
"externalUserId": "user_123",
"chainToken": "USDC",
"chain": "STARKNET"
},
headers={
"Authorization": "Bearer sk_prod_xxxx",
"x-api-key": "pk_xxxx",
}
)
balance = response.json()
human_readable = Decimal(balance["balance"]) / (Decimal(10) ** balance["decimals"])
```
```json 200 Response theme={null}
{
"chain": "STARKNET",
"chainToken": "USDC",
"chainTokenAddress": "0x033068F6539f8e6e6b131e6B2B814e6c34A5224bC66947c47DaB9dFeE93b35fb",
"decimals": 6,
"balance": "15000000"
}
```
## Notes
* The `balance` field is a raw integer string. Divide by `10^decimals` to get the human-readable amount. In the example above, `15000000 / 10^6 = 15.0 USDC`.
* You can identify the wallet by either `externalUserId` or `walletPublicKey`. At least one is required.
# Get Transaction List
Source: https://docs.chipipay.com/sdk/api/endpoint/get-transaction-list
GET /transactions/transaction-list
Get paginated transaction history for a wallet
```bash cURL theme={null}
curl -X GET "https://api.chipipay.com/v1/transactions/transaction-list?walletAddress=0x049d365...&page=1&limit=10" \
-H "Authorization: Bearer sk_prod_xxxx" \
-H "x-api-key: pk_xxxx"
```
```typescript TypeScript theme={null}
const params = new URLSearchParams({
walletAddress: "0x049d365...",
page: "1",
limit: "10"
});
const response = await fetch(
`https://api.chipipay.com/v1/transactions/transaction-list?${params}`,
{
headers: {
"Authorization": "Bearer sk_prod_xxxx",
"x-api-key": "pk_xxxx",
}
}
);
const { data, total, totalPages } = await response.json();
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.chipipay.com/v1/transactions/transaction-list",
params={
"walletAddress": "0x049d365...",
"page": 1,
"limit": 10
},
headers={
"Authorization": "Bearer sk_prod_xxxx",
"x-api-key": "pk_xxxx",
}
)
result = response.json()
transactions = result["data"]
```
```json 200 Response theme={null}
{
"data": [
{
"id": "tx_abc123",
"type": "SEND",
"chain": "STARKNET",
"transactionHash": "0x1234567890abcdef...",
"senderAddress": "0x049d365...",
"destinationAddress": "0x0abcdef...",
"token": "USDC",
"tokenAddress": "0x033068F6539f8e6e6b131e6B2B814e6c34A5224bC66947c47DaB9dFeE93b35fb",
"amount": "10500000",
"status": "SUCCESS",
"createdAt": "2025-03-15T10:30:00Z",
"confirmedAt": "2025-03-15T10:31:00Z",
"updatedAt": "2025-03-15T10:31:00Z",
"apiPublicKey": "pk_xxxx"
}
],
"total": 42,
"page": 1,
"limit": 10,
"totalPages": 5
}
```
## Date Filtering
You can filter transactions by date using `day`, `month`, and `year` parameters:
```bash theme={null}
# Get all transactions from March 2025
curl "https://api.chipipay.com/v1/transactions/transaction-list?walletAddress=0x...&month=3&year=2025"
# Get transactions from March 15, 2025
curl "https://api.chipipay.com/v1/transactions/transaction-list?walletAddress=0x...&day=15&month=3&year=2025"
```
## Function Filtering
Filter by contract function name to see specific types of transactions:
```bash theme={null}
# Only transfer transactions
curl "...&calledFunction=transfer"
# Only approve transactions
curl "...&calledFunction=approve"
```
# Get USD Amount
Source: https://docs.chipipay.com/sdk/api/endpoint/get-usd-amount
GET /exchanges/usd-amount
Convert an amount from MXN or other currency to USD
```bash cURL theme={null}
curl -X GET "https://api.chipipay.com/v1/exchanges/usd-amount?currencyAmount=250¤cy=MXN" \
-H "Authorization: Bearer sk_prod_xxxx" \
-H "x-api-key: pk_xxxx"
```
```typescript TypeScript theme={null}
const params = new URLSearchParams({
currencyAmount: "250",
currency: "MXN"
});
const response = await fetch(
`https://api.chipipay.com/v1/exchanges/usd-amount?${params}`,
{
headers: {
"Authorization": "Bearer sk_prod_xxxx",
"x-api-key": "pk_xxxx",
}
}
);
const usdAmount = await response.json(); // e.g., 13.42
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.chipipay.com/v1/exchanges/usd-amount",
params={"currencyAmount": 250, "currency": "MXN"},
headers={
"Authorization": "Bearer sk_prod_xxxx",
"x-api-key": "pk_xxxx",
}
)
usd_amount = response.json() # e.g., 13.42
```
```json 200 Response theme={null}
13.42
```
## Notes
* The response is a plain number, not wrapped in an object.
* Exchange rates are fetched in real-time. The rate may change between calls.
* Supported currencies: `MXN` (Mexican Peso), `USD` (US Dollar).
# Get Wallet
Source: https://docs.chipipay.com/sdk/api/endpoint/get-wallet
GET /chipi-wallets/by-user
Retrieve a wallet by external user ID
```bash cURL theme={null}
curl -X GET "https://api.chipipay.com/v1/chipi-wallets/by-user?externalUserId=user_123" \
-H "Authorization: Bearer sk_prod_xxxx" \
-H "x-api-key: pk_xxxx" \
-H "Content-Type: application/json"
```
```typescript TypeScript theme={null}
const response = await fetch(
"https://api.chipipay.com/v1/chipi-wallets/by-user?externalUserId=user_123",
{
method: "GET",
headers: {
"Authorization": "Bearer sk_prod_xxxx",
"x-api-key": "pk_xxxx",
"Content-Type": "application/json"
}
}
);
const wallet = await response.json();
```
```python Python theme={null}
import requests
response = requests.get(
"https://api.chipipay.com/v1/chipi-wallets/by-user",
params={"externalUserId": "user_123"},
headers={
"Authorization": "Bearer sk_prod_xxxx",
"x-api-key": "pk_xxxx",
}
)
wallet = response.json()
```
```json 200 Response theme={null}
{
"id": "wallet_abc123",
"externalUserId": "user_123",
"organizationId": "org_xyz",
"apiPublicKey": "pk_xxxx",
"publicKey": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
"normalizedPublicKey": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
"encryptedPrivateKey": "U2FsdGVkX1...",
"isDeployed": true,
"walletType": "CHIPI",
"chain": "STARKNET",
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-01-15T10:30:00Z"
}
```
```json 404 Not Found theme={null}
{
"error": "wallet_not_found",
"message": "No wallet found for this user"
}
```
# Record Send Transaction
Source: https://docs.chipipay.com/sdk/api/endpoint/record-send-transaction
POST /transactions/record-send
Record a token transfer that was executed on-chain
```bash cURL theme={null}
curl -X POST "https://api.chipipay.com/v1/transactions/record-send" \
-H "Authorization: Bearer sk_prod_xxxx" \
-H "x-api-key: pk_xxxx" \
-H "Content-Type: application/json" \
-d '{
"transactionHash": "0x1234567890abcdef...",
"expectedSender": "0x049d365...",
"expectedRecipient": "0x0abcdef...",
"expectedToken": "USDC",
"expectedAmount": "10.5",
"chain": "STARKNET"
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
"https://api.chipipay.com/v1/transactions/record-send",
{
method: "POST",
headers: {
"Authorization": "Bearer sk_prod_xxxx",
"x-api-key": "pk_xxxx",
"Content-Type": "application/json"
},
body: JSON.stringify({
transactionHash: "0x1234567890abcdef...",
expectedSender: "0x049d365...",
expectedRecipient: "0x0abcdef...",
expectedToken: "USDC",
expectedAmount: "10.5",
chain: "STARKNET"
})
}
);
const transaction = await response.json();
```
```python Python theme={null}
import requests
response = requests.post(
"https://api.chipipay.com/v1/transactions/record-send",
headers={
"Authorization": "Bearer sk_prod_xxxx",
"x-api-key": "pk_xxxx",
"Content-Type": "application/json"
},
json={
"transactionHash": "0x1234567890abcdef...",
"expectedSender": "0x049d365...",
"expectedRecipient": "0x0abcdef...",
"expectedToken": "USDC",
"expectedAmount": "10.5",
"chain": "STARKNET"
}
)
transaction = response.json()
```
```json 200 Response theme={null}
{
"id": "tx_abc123",
"type": "SEND",
"chain": "STARKNET",
"transactionHash": "0x1234567890abcdef...",
"senderAddress": "0x049d365...",
"destinationAddress": "0x0abcdef...",
"token": "USDC",
"tokenAddress": "0x033068F6539f8e6e6b131e6B2B814e6c34A5224bC66947c47DaB9dFeE93b35fb",
"amount": "10500000",
"status": "PENDING",
"createdAt": "2025-03-15T10:30:00Z",
"updatedAt": "2025-03-15T10:30:00Z",
"apiPublicKey": "pk_xxxx"
}
```
## Notes
* The `expectedAmount` is in human-readable format (e.g., `"10.5"` for 10.5 USDC). The API converts it to raw format internally.
* The transaction is verified on-chain. If the hash doesn't match the expected sender/recipient/amount, the request will fail.
# Update Wallet Encryption
Source: https://docs.chipipay.com/sdk/api/endpoint/update-wallet-encryption
PATCH /chipi-wallets/update-encryption-details
Replace the stored encrypted private key after a passkey or PIN change
```bash cURL theme={null}
curl -X PATCH "https://api.chipipay.com/v1/chipi-wallets/update-encryption-details" \
-H "Authorization: Bearer sk_prod_xxxx" \
-H "x-api-key: pk_xxxx" \
-H "Content-Type: application/json" \
-d '{
"externalUserId": "user_123",
"newEncryptedPrivateKey": "U2FsdGVkX1+newEncryptedKey..."
}'
```
```typescript TypeScript theme={null}
const response = await fetch(
"https://api.chipipay.com/v1/chipi-wallets/update-encryption-details",
{
method: "PATCH",
headers: {
"Authorization": "Bearer sk_prod_xxxx",
"x-api-key": "pk_xxxx",
"Content-Type": "application/json"
},
body: JSON.stringify({
externalUserId: "user_123",
newEncryptedPrivateKey: "U2FsdGVkX1+newEncryptedKey..."
})
}
);
```
```python Python theme={null}
import requests
response = requests.patch(
"https://api.chipipay.com/v1/chipi-wallets/update-encryption-details",
headers={
"Authorization": "Bearer sk_prod_xxxx",
"x-api-key": "pk_xxxx",
"Content-Type": "application/json"
},
json={
"externalUserId": "user_123",
"newEncryptedPrivateKey": "U2FsdGVkX1+newEncryptedKey..."
}
)
```
```json 200 Response theme={null}
{
"success": true
}
```
After calling this endpoint, the previous passkey or PIN used to encrypt the
private key will no longer work. Make sure the new encryption is persisted
before discarding the old key material.
# Introduction to Chipi Pay API
Source: https://docs.chipipay.com/sdk/api/introduction
REST API for creating self-custodial wallets, executing gasless transactions, and purchasing services on Starknet.
## Base URL
```bash theme={null}
https://api.chipipay.com/v1
```
## Authentication
All API requests require two headers:
| Header | Value | Description |
| --------------- | --------------------- | ------------------------------------------------------- |
| `x-api-key` | `pk_xxxx` | Your Chipi public API key |
| `Authorization` | `Bearer sk_prod_xxxx` | Your secret key (server-side) or user JWT (client-side) |
```bash theme={null}
curl -H "x-api-key: pk_xxxx" \
-H "Authorization: Bearer sk_prod_xxxx" \
https://api.chipipay.com/v1/skus
```
For detailed instructions on obtaining your API keys, see the [API Quickstart](/sdk/api/quickstart).
## API Endpoints
### Wallets
| Method | Endpoint | Description |
| ------- | ---------------------------------------------------------------------------------------- | ------------------------ |
| `GET` | [`/chipi-wallets/by-user`](/sdk/api/endpoint/get-wallet) | Get wallet by user ID |
| `GET` | [`/chipi-wallets/token-balance`](/sdk/api/endpoint/get-token-balance) | Get token balance |
| `PATCH` | [`/chipi-wallets/update-encryption-details`](/sdk/api/endpoint/update-wallet-encryption) | Update wallet encryption |
### Transactions
| Method | Endpoint | Description |
| ------ | -------------------------------------------------------------------------- | ------------------------- |
| `POST` | [`/transactions/record-send`](/sdk/api/endpoint/record-send-transaction) | Record a send transaction |
| `GET` | [`/transactions/transaction-list`](/sdk/api/endpoint/get-transaction-list) | Get transaction history |
### Services (SKUs)
| Method | Endpoint | Description |
| ------ | ----------------------------------------------------------- | ----------------------- |
| `GET` | [`/skus`](/sdk/api/endpoint/get-skus) | List available services |
| `GET` | [`/skus/{id}`](/sdk/api/endpoint/get-sku) | Get a single service |
| `POST` | [`/sku-purchases`](/sdk/api/endpoint/buy-sku) | Purchase a service |
| `GET` | [`/sku-purchases/{id}`](/sdk/api/endpoint/get-sku-purchase) | Get purchase status |
### Exchanges
| Method | Endpoint | Description |
| ------ | ----------------------------------------------------------- | ----------------------- |
| `GET` | [`/exchanges/usd-amount`](/sdk/api/endpoint/get-usd-amount) | Convert currency to USD |
## Notifications and Webhooks
When you purchase a service, you'll receive status updates via webhooks or email at the URL and email address you configured in your dashboard.
### Steps to set up your webhook
* For setting up webhooks and email notifications, see the [API Quickstart](/sdk/api/quickstart).
## Rate Limits
API requests are limited to 100 requests per minute per API key.
## Error Handling
The API uses standard HTTP status codes to indicate success or failure:
* `200` - Success
* `400` - Bad Request
* `401` - Unauthorized
* `404` - Not Found
* `429` - Rate Limited
* `500` - Internal Server Error
Error responses include a JSON object with an `error` and `message` field:
```json theme={null}
{
"error": "invalid_request",
"message": "The request was invalid"
}
```
# API Quickstart
Source: https://docs.chipipay.com/sdk/api/quickstart
Get started with the Chipi API - learn how to authenticate, make requests, and handle responses
## Base URL
```bash theme={null}
https://api.chipipay.com/v1
```
## Authentication
All API requests require authentication using your API key. Include it in the `Authorization` header:
```bash theme={null}
Authorization: Bearer YOUR_API_KEY
```
### Getting Your API Key
To use the Chipi Pay API, you'll need to obtain your API key from the dashboard.
#### Steps to Get Your API Key:
Go to the [Configure API Keys](https://dashboard.chipipay.com/configure/api-keys) section in your dashboard.
Inside you will see API Keys:
* Public Key: Can be safely shared and is used to identify your application
* **Secret Key**: This is your API Key. This is the key you will use to authenticate all the calls to our API
Never commit your API key to version control or share it publicly. Always use environment variables or secure configuration management.
## Webhooks
You can set up your webhook to receive notifications [here](https://dashboard.chipipay.com/configure/webhooks)
## Email Notifications
You can set up an email to receive notifications [here](https://dashboard.chipipay.com/configure/notifications)
## Testing
You have two environments in your dashboard **Production** and **Development**.
Remember to Do your tests using the Development environment.
Sandbox API keys are clearly marked and won't process real transactions.
## Support
Need help? We're here to assist:
* **Documentation**: Browse our comprehensive guides
* **Community**: Join our [Telegram group](https://t.me/+e2qjHEOwImkyZDVh)
* **Email**: [support@chipipay.com](mailto:support@chipipay.com)
* **Dashboard**: [dashboard.chipipay.com](https://dashboard.chipipay.com)
# Quickstart
Source: https://docs.chipipay.com/sdk/api/wallets-quickstart
Quick setup guide for gasless transactions using only the Chipi API
You'll need to configure your auth provider's JWKS endpoint in the [Chipi Dashboard](https://dashboard.chipipay.com). The dashboard quickstart detects your auth provider and walks you through the setup.
With this JWKS Endpoint setup, you can now run secure code in your frontend
using a rotating JWT token.
### Common auth providers
The dashboard auto-configures JWKS when you provide your Clerk Secret Key.
See our tutorial on how to set up Chipi with [Firebase](/sdk/nextjs/gasless-firebase-setup).
See our tutorial on how to set up Chipi with [Supabase](/sdk/react/gasless-supabase-setup).
Go to the [Chipi Dashboard](https://dashboard.chipipay.com) and open your project. The quickstart shows your `.env` snippet with both keys ready to copy.
You'll need:
* **Public Key** (`pk_prod_xxxx`) — used client-side
* **Secret Key** (`sk_prod_xxxx`) — used by the server-side `ChipiProvider`
Since this wallet is self-custodial, there are many steps that need to be done in the frontend.
Here is the code to create a wallet for your user.
Chipi supports two wallet types:
* **CHIPI** (default): OpenZeppelin account with SNIP-9 session keys support
* **READY**: Argent X Account v0.4.0
```typescript theme={null}
// External dependencies (you'll need to install these)
import CryptoJS from "crypto-js";
import {
Account,
CairoCustomEnum,
CairoOption,
CairoOptionVariant,
CallData,
ec,
hash,
num,
RpcProvider,
stark,
} from "starknet";
// Wallet type configuration
type WalletType = "CHIPI" | "READY";
// Class hashes for each wallet type
const WALLET_CLASS_HASHES = {
CHIPI: "0x53f4f8791ed5bed0fddaa553d180c664e32cfaf8316bb232ae77bb08f459f2a",
READY: "0x036078334509b514626504edc9fb252328d1a240e4e948bef8d0c08dff45927f",
};
// RPC endpoints per wallet type
const WALLET_RPC_ENDPOINTS = {
CHIPI: "https://starknet-mainnet.public.blastapi.io/rpc/v0_7",
READY: "https://cloud.argent-api.com/v1/starknet/mainnet/rpc/v0.7",
};
// Deployment data type
interface DeploymentData {
class_hash: string;
salt: string;
unique: string;
calldata: string[];
}
// Encryption utility
const encryptPrivateKey = (
privateKey: string,
password: string,
): string => {
if (!privateKey || !password) {
throw new Error("Private key and password are required");
}
return CryptoJS.AES.encrypt(privateKey, password).toString();
};
// Types (included inline)
interface WalletData {
publicKey: string;
encryptedPrivateKey: string;
}
interface CreateWalletParams {
encryptKey: string;
externalUserId: string;
apiPublicKey: string;
bearerToken: string;
chain: string; // Currently only "STARKNET" is supported
walletType?: WalletType; // Optional, defaults to "CHIPI"
}
// Backend URL constant
const BACKEND_URL = "https://api.chipipay.com/v1";
// Build constructor calldata based on wallet type
const buildConstructorCallData = (
walletType: WalletType,
starkKeyPubAX: string
): string[] => {
if (walletType === "READY") {
// Argent X Account: owner (CairoCustomEnum) + guardian (CairoOption None)
const axSigner = new CairoCustomEnum({
Starknet: { pubkey: starkKeyPubAX },
});
const axGuardian = new CairoOption(CairoOptionVariant.None);
return CallData.compile({
owner: axSigner,
guardian: axGuardian,
});
}
// ChipiWallet (default): Simple OpenZeppelin account with just public_key
return CallData.compile({
public_key: starkKeyPubAX,
});
};
// Main function
export const createWallet = async (
params: CreateWalletParams
): Promise => {
try {
const {
encryptKey,
apiPublicKey,
bearerToken,
walletType = "CHIPI" // Default to CHIPI wallet
} = params;
// Select RPC endpoint based on wallet type
const rpcUrl = WALLET_RPC_ENDPOINTS[walletType];
const provider = new RpcProvider({ nodeUrl: rpcUrl });
// Generating the private key with Stark Curve
const privateKeyAX = stark.randomAddress();
const starkKeyPubAX = ec.starkCurve.getStarkKey(privateKeyAX);
// Select class hash based on wallet type
const accountClassHash = WALLET_CLASS_HASHES[walletType];
// Build constructor calldata based on wallet type
const constructorCallData = buildConstructorCallData(walletType, starkKeyPubAX);
// Calculate future address of the account
const publicKey = hash.calculateContractAddressFromHash(
starkKeyPubAX,
accountClassHash,
constructorCallData,
0
);
// Initiating Account
const account = new Account(provider, publicKey, privateKeyAX);
// Backend Call API to create the wallet
const typeDataResponse = await fetch(`${BACKEND_URL}/chipi-wallets/prepare-creation`, {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearerToken}`,
'x-api-key': apiPublicKey,
},
body: JSON.stringify({
publicKey,
walletType,
}),
});
const { typeData, accountClassHash: accountClassHashResponse } = await typeDataResponse.json();
// Sign the message
const userSignature = await account.signMessage(typeData);
const deploymentData: DeploymentData = {
class_hash: accountClassHashResponse,
salt: starkKeyPubAX,
unique: `${num.toHex(0)}`,
calldata: constructorCallData.map((value: any) => num.toHex(value)),
};
const encryptedPrivateKey = encryptPrivateKey(privateKeyAX, encryptKey);
// Call the API to save the wallet in dashboard
const executeTransactionResponse = await fetch(`${BACKEND_URL}/chipi-wallets`, {
method: "POST",
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${bearerToken}`,
'x-api-key': apiPublicKey,
},
body: JSON.stringify({
apiPublicKey,
publicKey,
walletType,
userSignature: {
r: (userSignature as any).r.toString(),
s: (userSignature as any).s.toString(),
recovery: (userSignature as any).recovery
},
typeData,
encryptedPrivateKey,
deploymentData: {
...deploymentData,
salt: `${deploymentData.salt}`,
calldata: deploymentData.calldata.map((data: any) => `${data}`),
}
}),
});
const executeTransaction = await executeTransactionResponse.json();
console.log("Execute transaction: ", executeTransaction);
if (executeTransaction.success) {
return {
success: true,
txHash: executeTransaction.txHash,
wallet: {
publicKey: executeTransaction.walletPublicKey,
encryptedPrivateKey: encryptedPrivateKey,
} as WalletData,
};
} else {
return {
success: false,
txHash: "",
wallet: {
publicKey: "",
encryptedPrivateKey: "",
} as WalletData,
};
}
} catch (error: unknown) {
console.error("Error detallado:", error);
if (error instanceof Error && error.message.includes("SSL")) {
throw new Error(
"SSL connection error. Try using NODE_TLS_REJECT_UNAUTHORIZED=0 or verify the RPC URL"
);
}
throw new Error(
`Error creating wallet: ${
error instanceof Error ? error.message : "Unknown error"
}`
);
}
};
```
Now you can create a wallet for your user.
For the bearerToken you need to pass a valid JWT token from your auth provider.
### Create a ChipiWallet (Default)
ChipiWallet is an OpenZeppelin account with SNIP-9 session keys support, perfect for gasless transactions and delegated access.
```typescript theme={null}
// Example usage with ChipiWallet (default):
const params: CreateWalletParams = {
encryptKey: "user-secure-pin", // Encryption key for the private key
externalUserId: "user-123", // Your user's unique identifier
apiPublicKey: "pk_prod_***", // Your Chipi Pay public key
bearerToken: "your-bearer-token", // Valid JWT token from your Auth Provider
chain: "STARKNET", // Currently only STARKNET is supported
// walletType: "CHIPI", // Optional - CHIPI is the default
};
createWallet(params)
.then(response => console.log("Wallet created:", response))
.catch(error => console.error("Error:", error));
```
### Create a Ready Wallet
If you need Argent X compatibility:
```typescript theme={null}
// Example usage with Ready wallet:
const params: CreateWalletParams = {
encryptKey: "user-secure-pin",
externalUserId: "user-123",
apiPublicKey: "pk_prod_***",
bearerToken: "your-bearer-token",
chain: "STARKNET", // Currently only STARKNET is supported
walletType: "READY", // Explicitly use Argent X Account
};
createWallet(params)
.then(response => console.log("Wallet created:", response))
.catch(error => console.error("Error:", error));
```
**Backward Compatibility**: If you don't specify `walletType`, the SDK will create a ChipiWallet by default. Existing integrations will continue to work without changes.
# callAnyContract
Source: https://docs.chipipay.com/sdk/backend/methods/call-any-contract
Executes any StarkNet contract method. Handles all contract interactions not covered by specific methods, giving you full flexibility to interact with any smart contract.
## Usage
```typescript theme={null}
const result = await serverClient.callAnyContract({
params: {
encryptKey: "user-secure-pin",
wallet: {
publicKey: "0x123...yourPublicKeyHere",
encryptedPrivateKey: "encrypted:key:data"
},
calls: [
{
contractAddress: "0x...", // Target contract address
entrypoint: "methodName",
calldata: ["param1", "param2"]
}
],
},
});
```
### Parameters
* `encryptKey` (string): User's decryption PIN
* `wallet` (WalletData): Wallet credentials with publicKey and encryptedPrivateKey
* `calls` (array): Array of contract calls to execute
* `contractAddress` (string): Target contract address
* `entrypoint` (string): Contract method name
* `calldata` (any\[]): Arguments for the contract method
### Return Value
Returns a Promise that resolves to a transaction hash string.
## Example Implementation
```typescript theme={null}
import { ChipiServerSDK } from "@chipi-stack/backend";
const serverClient = new ChipiServerSDK({
apiPublicKey: process.env.CHIPI_PUBLIC_KEY!,
apiSecretKey: process.env.CHIPI_SECRET_KEY!,
});
async function executeContractCall(
wallet: { publicKey: string; encryptedPrivateKey: string },
contractAddress: string,
methodName: string,
parameters: any[],
userPin: string
) {
try {
const result = await serverClient.callAnyContract({
params: {
encryptKey: userPin,
wallet: wallet,
calls: [
{
contractAddress: contractAddress,
entrypoint: methodName,
calldata: parameters,
}
],
},
});
console.log('Contract call successful!');
console.log('Transaction hash:', result);
return result;
} catch (error) {
console.error('Contract call failed:', error);
throw error;
}
}
// Usage example - Approve tokens for spending
async function approveTokenSpending(
userId: string,
tokenContract: string,
spenderAddress: string,
amount: string,
pin: string
) {
// Get user's wallet
const wallet = await serverClient.getWallet({
externalUserId: userId,
});
// Call the approve method on the token contract
const txHash = await executeContractCall(
wallet,
tokenContract,
"approve",
[spenderAddress, amount, "0"], // StarkNet uses felt252 for amounts
pin
);
console.log(`Approved ${amount} tokens for ${spenderAddress}`);
return txHash;
}
```
# createWallet
Source: https://docs.chipipay.com/sdk/backend/methods/create-wallet
Creates a new wallet on StarkNet. Deploys the wallet contract and sponsors gas fees via the Chipi paymaster, resulting in a frictionless onboarding experience.
**PIN is weak — not recommended for production.**
A user-typed PIN is a short, low-entropy string. Anyone who shoulder-surfs the PIN, observes a phishing form, or compromises the browser at typing time can decrypt the wallet's private key. PIN remains in the SDK only as a fallback recovery surface for users who lose access to their platform authenticator.
**Production embedded-wallet apps should default to a platform passkey** (Touch ID, Face ID, Windows Hello, Android biometrics) via the `@chipi-stack/chipi-passkey` package. For SHHH V8.4 wallets, `signerKind: "WEBAUTHN_P256"` keeps the private key inside the platform authenticator — it never leaves the device, never reaches Chipi servers, and is never derived from a user-typed secret.
Only prompt for a PIN as the encryption key when:
* The user explicitly opted into a PIN-only flow (e.g. cold-storage / paper-backup recovery), or
* The platform genuinely has no WebAuthn / biometric support available.
If you are migrating an existing PIN-based wallet to a passkey, look up `useMigrateWalletToPasskey` in your framework's hook docs.
## Usage
```typescript theme={null}
// Default CHIPI wallet (session keys, passkey support)
const newWallet = await serverClient.createWallet({
params: {
encryptKey: "user-secure-pin",
externalUserId: "your-user-id-123",
chain: Chain.STARKNET,
},
});
// READY wallet (Argent X compatible, no session keys)
const readyWallet = await serverClient.createWallet({
params: {
encryptKey: "user-secure-pin",
externalUserId: "your-user-id-456",
chain: Chain.STARKNET,
walletType: "READY",
},
});
// Custom account implementation (advanced)
const customWallet = await serverClient.createWallet({
params: {
encryptKey: "user-secure-pin",
externalUserId: "your-user-id-789",
chain: Chain.STARKNET,
classHash: "0x0484bbd2404b3c7264bea271f7267d6d4004821ac7787a9eed7f472e79ef40d1",
},
});
```
### Parameters
| Parameter | Type | Required | Description |
| ---------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `encryptKey` | string | Yes | User-defined code or password to encrypt the wallet's private key |
| `externalUserId` | string | Yes | Your application's unique identifier for the user |
| `chain` | `Chain` | Yes | Blockchain network. Use `Chain.STARKNET` |
| `walletType` | string | No | `"CHIPI"` (default) or `"READY"`. CHIPI supports session keys and passkeys. READY is Argent X compatible. |
| `classHash` | string | No | Custom StarkNet class hash for the wallet contract. Overrides the default for the wallet type. Must be declared on mainnet and implement SNIP-9. See [Custom Wallet Types](/sdk/guides/custom-wallet-types). |
| `usePasskey` | boolean | No | Use WebAuthn passkey for encryption instead of PIN |
### Return Value
Returns a Promise that resolves to an object containing:
* `publicKey`: The wallet's StarkNet address
* `encryptedPrivateKey`: The encrypted private key (store securely)
* `walletType`: The wallet type that was created
* `classHash`: The class hash used for deployment
## Example Implementation
```typescript theme={null}
import { ChipiServerSDK, Chain } from "@chipi-stack/backend";
const serverClient = new ChipiServerSDK({
apiPublicKey: process.env.CHIPI_PUBLIC_KEY!,
apiSecretKey: process.env.CHIPI_SECRET_KEY!,
});
async function createUserWallet(userId: string, userPin: string) {
const newWallet = await serverClient.createWallet({
params: {
encryptKey: userPin,
externalUserId: userId,
chain: Chain.STARKNET,
},
});
console.log("Wallet created:", newWallet.publicKey);
// Store wallet data in your database
await saveWalletToDatabase({
userId,
publicKey: newWallet.publicKey,
encryptedPrivateKey: newWallet.encryptedPrivateKey,
});
return newWallet;
}
```
Wallet creation is free! Gas fees are sponsored by the Chipi paymaster for all wallet types.
## Wallet Types
| Type | Account | Session Keys | Passkeys | Use Case |
| ------- | --------------------- | ------------ | -------- | -------------------------------- |
| `CHIPI` | OpenZeppelin + SNIP-9 | Yes | Yes | Default. Best for most apps. |
| `READY` | Argent X v0.4.0 | No | No | Argent X ecosystem compatibility |
| Custom | Any SNIP-9 account | Depends | Depends | Advanced: bring your own account |
## Related Methods
* [getWallet](/sdk/backend/methods/get-wallet) - Retrieve existing wallet information
* [transfer](/sdk/backend/methods/transfer) - Send tokens from the created wallet
* [prepareWalletUpgrade](/sdk/backend/methods/prepare-wallet-upgrade) - Upgrade wallet to a different type
* [Custom Wallet Types Guide](/sdk/guides/custom-wallet-types) - Using custom account implementations
# executeWalletUpgrade
Source: https://docs.chipipay.com/sdk/backend/methods/execute-wallet-upgrade
Executes a wallet upgrade after the owner has signed the typed data from prepareWalletUpgrade.
Server-only method. Requires API secret key.
## Usage
```typescript theme={null}
const result = await sdk.executeWalletUpgrade(
{
walletAddress: "0x...",
typedData: upgrade.typedData,
signature: ["0xr", "0xs"],
},
bearerToken,
);
```
## Parameters
| Parameter | Type | Required | Description |
| --------------- | ----------- | -------- | ------------------------------------------------------- |
| `walletAddress` | `string` | Yes | Wallet address being upgraded |
| `typedData` | `TypedData` | Yes | SNIP-12 typed data from `prepareWalletUpgrade` |
| `signature` | `string[]` | Yes | User signature over the typed data |
| `bearerToken` | `string` | No | JWT token. Falls back to `apiSecretKey` if not provided |
## Return Value
Returns a `Promise`:
| Field | Type | Description |
| ----------------- | ------------ | -------------------------------- |
| `transactionHash` | `string` | Transaction hash for the upgrade |
| `newClassHash` | `string` | New class hash after upgrade |
| `newWalletType` | `WalletType` | New wallet type after upgrade |
## Example
See [`prepareWalletUpgrade`](/sdk/backend/methods/prepare-wallet-upgrade) for a complete upgrade flow.
# getTokenBalance
Source: https://docs.chipipay.com/sdk/backend/methods/get-token-balance
Fetches the on-chain token balance for a wallet on Starknet.
## Usage
```typescript theme={null}
const balance = await sdk.getTokenBalance(
{
chainToken: ChainToken.USDC,
chain: Chain.STARKNET,
walletPublicKey: "0x...",
},
bearerToken,
);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------- | ------------ | -------- | ------------------------------------------------------- |
| `chainToken` | `ChainToken` | Yes | Token identifier (e.g. `"USDC"`) |
| `chain` | `Chain` | Yes | Blockchain network. Use `Chain.STARKNET` |
| `walletPublicKey` | `string` | No | Wallet address. Use this or `externalUserId` |
| `externalUserId` | `string` | No | External user ID. Use this or `walletPublicKey` |
| `bearerToken` | `string` | No | JWT token. Falls back to `apiSecretKey` if not provided |
## Return Value
Returns a `Promise` with the balance data.
## Example
```typescript theme={null}
import { ChipiServerSDK, Chain, ChainToken } from "@chipi-stack/backend";
const sdk = new ChipiServerSDK({
apiPublicKey: process.env.CHIPI_PUBLIC_KEY!,
apiSecretKey: process.env.CHIPI_SECRET_KEY!,
});
const balance = await sdk.getTokenBalance(
{
chainToken: ChainToken.USDC,
chain: Chain.STARKNET,
walletPublicKey: "0x04...abc",
},
bearerToken,
);
console.log("Balance:", balance);
```
# getTransaction
Source: https://docs.chipipay.com/sdk/backend/methods/get-transaction
Fetches a single transaction by its hash or internal ID.
## Usage
```typescript theme={null}
const transaction = await sdk.getTransaction("0x...", bearerToken);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------- | -------- | -------- | ------------------------------------------------------- |
| `hashOrId` | `string` | Yes | Transaction hash (`0x...`) or internal database ID |
| `bearerToken` | `string` | No | JWT token. Falls back to `apiSecretKey` if not provided |
## Return Value
Returns a `Promise` with the full transaction record including `id`, `transactionHash`, `status`, `senderAddress`, `destinationAddress`, `amount`, `token`, and timestamps.
## Example
```typescript theme={null}
// Fetch by transaction hash
const tx = await sdk.getTransaction(
"0x04abc...def",
bearerToken,
);
console.log(tx.status, tx.amount, tx.token);
// Fetch by internal ID
const tx2 = await sdk.getTransaction("tx-123", bearerToken);
```
# getTransactionList
Source: https://docs.chipipay.com/sdk/backend/methods/get-transaction-list
Fetches a paginated list of transactions with optional filtering.
## Usage
```typescript theme={null}
const result = await sdk.getTransactionList(
{
page: 1,
limit: 10,
walletAddress: "0x...",
},
bearerToken,
);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------- | -------- | -------- | ------------------------------------------------------- |
| `page` | `number` | No | Page number (default: `1`) |
| `limit` | `number` | No | Results per page (default: `10`) |
| `walletAddress` | `string` | No | Filter by wallet address |
| `calledFunction` | `string` | No | Filter by contract function name |
| `day` | `number` | No | Filter by day (1–31) |
| `month` | `number` | No | Filter by month (1–12) |
| `year` | `number` | No | Filter by year |
| `bearerToken` | `string` | No | JWT token. Falls back to `apiSecretKey` if not provided |
## Return Value
Returns a `Promise>` with `items`, `total`, `page`, and `limit`.
## Example
```typescript theme={null}
const result = await sdk.getTransactionList(
{
page: 1,
limit: 20,
walletAddress: "0x04...abc",
month: 2,
year: 2026,
},
bearerToken,
);
console.log(`Found ${result.total} transactions`);
result.items.forEach((tx) => {
console.log(tx.transactionHash, tx.status);
});
```
# getTransactionStatus
Source: https://docs.chipipay.com/sdk/backend/methods/get-transaction-status
Fetches the on-chain status of a transaction from StarkNet. Useful for polling until confirmation.
## Usage
```typescript theme={null}
const status = await sdk.getTransactionStatus("0x...", bearerToken);
```
## Parameters
| Parameter | Type | Required | Description |
| ------------- | -------- | -------- | ------------------------------------------------------- |
| `hash` | `string` | Yes | Transaction hash (`0x...`) |
| `bearerToken` | `string` | No | JWT token. Falls back to `apiSecretKey` if not provided |
## Return Value
Returns a `Promise`:
| Field | Type | Description |
| ----------------- | --------------------- | ----------------------------------- |
| `transactionHash` | `string` | Transaction hash |
| `status` | `OnChainTxStatus` | Current on-chain status (see below) |
| `blockNumber` | `number \| undefined` | Block number if included in a block |
| `revertReason` | `string \| undefined` | Reason if status is `REVERTED` |
### OnChainTxStatus Values
| Status | Description | Terminal |
| ---------------- | ---------------------------- | -------- |
| `RECEIVED` | Transaction received by node | No |
| `PENDING` | Waiting for inclusion | No |
| `ACCEPTED_ON_L2` | Confirmed on StarkNet L2 | No |
| `ACCEPTED_ON_L1` | Proven on Ethereum L1 | Yes |
| `REJECTED` | Rejected by sequencer | Yes |
| `REVERTED` | Execution reverted on-chain | Yes |
| `NOT_RECEIVED` | Not found on network | Yes |
## Example
```typescript theme={null}
// Poll for confirmation
const poll = async (hash: string) => {
const terminalStatuses = ["ACCEPTED_ON_L1", "REJECTED", "REVERTED", "NOT_RECEIVED"];
const MAX_RETRIES = 60; // 60 retries * 3s = 3 minutes max
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
const { status, revertReason } = await sdk.getTransactionStatus(hash, bearerToken);
if (terminalStatuses.includes(status)) {
if (status === "REVERTED") console.error("Reverted:", revertReason);
return status;
}
await new Promise((r) => setTimeout(r, 3000));
}
throw new Error(`Transaction ${hash} did not reach terminal status after ${MAX_RETRIES} attempts`);
};
```
# getUsdAmount
Source: https://docs.chipipay.com/sdk/backend/methods/get-usd-amount
Converts a currency amount to its USD equivalent using current exchange rates.
## Usage
```typescript theme={null}
const usdAmount = await sdk.getUsdAmount(100, "MXN", bearerToken);
```
## Parameters
| Parameter | Type | Required | Description |
| ---------------- | ---------- | -------- | ------------------------------------------------------- |
| `currencyAmount` | `number` | Yes | The amount in the source currency |
| `currency` | `Currency` | Yes | The source currency code (e.g. `"MXN"`) |
| `bearerToken` | `string` | No | JWT token. Falls back to `apiSecretKey` if not provided |
## Return Value
Returns a `Promise` — the equivalent amount in USD.
## Example
```typescript theme={null}
const usdAmount = await sdk.getUsdAmount(500, "MXN", bearerToken);
console.log(`500 MXN = $${usdAmount} USD`);
```
Exchange rates are fetched in real-time. Results may vary between calls.
# getWallet
Source: https://docs.chipipay.com/sdk/backend/methods/get-wallet
Retrieves wallet information for an existing user by their external user ID. This method allows you to fetch wallet details for users who have already created wallets.
## Usage
```typescript theme={null}
const wallet = await serverClient.getWallet({
externalUserId: "your-user-id-123",
});
```
### Parameters
* `externalUserId` (string): Your application's unique identifier for the user
### Return Value
Returns a Promise that resolves to an object containing:
* `publicKey` (string): The wallet's public address on StarkNet
* `encryptedPrivateKey` (string): The encrypted private key for the wallet
## Example Implementation
```typescript theme={null}
import { ChipiServerSDK } from "@chipi-stack/backend";
const serverClient = new ChipiServerSDK({
apiPublicKey: process.env.CHIPI_PUBLIC_KEY!,
apiSecretKey: process.env.CHIPI_SECRET_KEY!,
});
async function getUserWallet(userId: string) {
try {
const wallet = await serverClient.getWallet({
externalUserId: userId,
});
console.log('Wallet found:');
console.log('Address:', wallet.publicKey);
return wallet;
} catch (error) {
if (error.message.includes('not found')) {
console.log('No wallet found for user:', userId);
return null;
}
console.error('Error retrieving wallet:', error);
throw error;
}
}
// Usage example
async function checkUserWallet(userId: string) {
const wallet = await getUserWallet(userId);
if (wallet) {
console.log(`User ${userId} has wallet: ${wallet.publicKey}`);
return wallet;
} else {
console.log(`User ${userId} needs to create a wallet`);
return null;
}
}
```
## Security Considerations
**Important Security Notes:**
* Never expose encrypted private keys in API responses
* Validate user IDs before making requests
* Implement proper access controls in your application
* Use HTTPS for all communications
* Log wallet access for audit purposes
Chipi never stores your decrypted sensitive data. All private keys remain encrypted and are never accessible to Chipi servers.
## Related Methods
* [createWallet](/sdk/backend/methods/create-wallet) - Create a new wallet for users who don't have one
* [transfer](/sdk/backend/methods/transfer) - Send tokens from the retrieved wallet
# prepareWalletUpgrade
Source: https://docs.chipipay.com/sdk/backend/methods/prepare-wallet-upgrade
Prepares a wallet upgrade (e.g., READY to CHIPI or CHIPI v29 to v33). Returns typed data that must be signed by the wallet owner.
Server-only method. Requires API secret key.
## Usage
```typescript theme={null}
const upgrade = await sdk.prepareWalletUpgrade(
{ walletAddress: "0x..." },
bearerToken,
);
```
## Parameters
| Parameter | Type | Required | Description |
| ----------------- | -------- | -------- | ------------------------------------------------------- |
| `walletAddress` | `string` | Yes | Wallet address to upgrade |
| `targetClassHash` | `string` | No | Target class hash (defaults to latest CHIPI) |
| `bearerToken` | `string` | No | JWT token. Falls back to `apiSecretKey` if not provided |
## Return Value
Returns a `Promise`:
| Field | Type | Description |
| ------------------- | ------------ | -------------------------------- |
| `typedData` | `TypedData` | SNIP-12 typed data to sign |
| `currentClassHash` | `string` | Current class hash of the wallet |
| `targetClassHash` | `string` | Target class hash for upgrade |
| `currentWalletType` | `WalletType` | Current wallet type |
| `targetWalletType` | `WalletType` | Target wallet type after upgrade |
## Example
```typescript theme={null}
// Prepare upgrade from READY to CHIPI
const upgrade = await sdk.prepareWalletUpgrade(
{ walletAddress: "0x04abc...def" },
bearerToken,
);
console.log(upgrade.currentWalletType); // "READY"
console.log(upgrade.targetWalletType); // "CHIPI"
// Sign the typed data (using starknet.js Account)
const signature = await account.signMessage(upgrade.typedData);
// Execute the upgrade
const result = await sdk.executeWalletUpgrade(
{
walletAddress: "0x04abc...def",
typedData: upgrade.typedData,
signature: [signature.r.toString(), signature.s.toString()],
},
bearerToken,
);
```
# Session Key Methods
Source: https://docs.chipipay.com/sdk/backend/methods/sessions
API reference for session key management - create, register, execute, query, and revoke delegated access
**PIN is weak — not recommended for production.**
A user-typed PIN is a short, low-entropy string. Anyone who shoulder-surfs the PIN, observes a phishing form, or compromises the browser at typing time can decrypt the wallet's private key. PIN remains in the SDK only as a fallback recovery surface for users who lose access to their platform authenticator.
**Production embedded-wallet apps should default to a platform passkey** (Touch ID, Face ID, Windows Hello, Android biometrics) via the `@chipi-stack/chipi-passkey` package. For SHHH V8.4 wallets, `signerKind: "WEBAUTHN_P256"` keeps the private key inside the platform authenticator — it never leaves the device, never reaches Chipi servers, and is never derived from a user-typed secret.
Only prompt for a PIN as the encryption key when:
* The user explicitly opted into a PIN-only flow (e.g. cold-storage / paper-backup recovery), or
* The platform genuinely has no WebAuthn / biometric support available.
If you are migrating an existing PIN-based wallet to a passkey, look up `useMigrateWalletToPasskey` in your framework's hook docs.
Session keys enable temporary, delegated transaction execution for [CHIPI wallets](https://github.com/chipi-pay/sessions-smart-contract). This page documents all available session methods.
***
## sessions.createSessionKey()
Generate a session keypair locally. The SDK returns the data but does NOT store it.
### Usage
```typescript theme={null}
const session = sdk.sessions.createSessionKey({
encryptKey: "user-secure-pin",
durationSeconds: 3600, // Optional, default: 21600 (6 hours)
});
```
### Parameters
| Parameter | Type | Required | Description |
| ----------------- | ------ | -------- | ----------------------------------------------------- |
| `encryptKey` | string | Yes | PIN to encrypt the session private key |
| `durationSeconds` | number | No | Session duration in seconds. Default: 21600 (6 hours) |
### Return Value
Returns a `SessionKeyData` object:
```typescript theme={null}
{
publicKey: string; // Stark public key (hex format)
encryptedPrivateKey: string; // AES encrypted private key
validUntil: number; // Unix timestamp (seconds)
}
```
### Example
```typescript theme={null}
// Create a 1-hour gaming session
const session = sdk.sessions.createSessionKey({
encryptKey: userPin,
durationSeconds: 60 * 60, // 1 hour
});
// Store in client-side secure storage (NOT your database!)
localStorage.setItem('chipiSession', JSON.stringify(session));
console.log(`Session expires at: ${new Date(session.validUntil * 1000)}`);
```
Store session data only in client-side secure storage (localStorage, Secure Enclave, Keychain). Never in your backend database.
***
## sessions.addSessionKeyToContract()
Register a session key on the [CHIPI wallet](https://github.com/chipi-pay/sessions-smart-contract) smart contract. Requires owner signature.
### Usage
```typescript theme={null}
const txHash = await sdk.sessions.addSessionKeyToContract({
encryptKey: "user-secure-pin",
wallet: {
publicKey: "0x...",
encryptedPrivateKey: "...",
walletType: "CHIPI",
},
sessionConfig: {
sessionPublicKey: session.publicKey,
validUntil: session.validUntil,
maxCalls: 100,
allowedEntrypoints: [],
},
}, bearerToken);
```
### Parameters
| Parameter | Type | Required | Description |
| ---------------------------------- | ---------- | -------- | --------------------------------------------- |
| `encryptKey` | string | Yes | PIN to decrypt owner's private key |
| `wallet` | WalletData | Yes | User's wallet data with `walletType: "CHIPI"` |
| `sessionConfig.sessionPublicKey` | string | Yes | Public key from `createSessionKey()` |
| `sessionConfig.validUntil` | number | Yes | Unix timestamp when session expires |
| `sessionConfig.maxCalls` | number | Yes | Maximum transactions allowed |
| `sessionConfig.allowedEntrypoints` | string\[] | Yes | Whitelisted selectors. Empty = all allowed |
| `bearerToken` | string | Yes | Valid JWT from your auth provider |
### Return Value
Returns a `Promise` with the transaction hash.
### Example
```typescript theme={null}
// Register session with restricted permissions
const txHash = await sdk.sessions.addSessionKeyToContract({
encryptKey: userPin,
wallet: {
publicKey: wallet.publicKey,
encryptedPrivateKey: wallet.encryptedPrivateKey,
walletType: "CHIPI",
},
sessionConfig: {
sessionPublicKey: session.publicKey,
validUntil: session.validUntil,
maxCalls: 50,
allowedEntrypoints: [
"0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e", // transfer
],
},
}, bearerToken);
console.log(`Session registered: https://starkscan.co/tx/${txHash}`);
```
This uses gasless transactions via the Chipi paymaster. Users don't need STRK for gas.
***
## executeTransactionWithSession()
Execute a transaction using a session key instead of the owner's private key.
### Usage
```typescript theme={null}
const txHash = await sdk.executeTransactionWithSession({
params: {
encryptKey: "user-secure-pin",
wallet: {
publicKey: "0x...",
encryptedPrivateKey: "...",
walletType: "CHIPI",
},
session: {
publicKey: "0x...",
encryptedPrivateKey: "...",
validUntil: 1702500000,
},
calls: [
{
contractAddress: "0x...",
entrypoint: "transfer",
calldata: ["0x...", "100", "0x0"],
},
],
},
bearerToken,
});
```
### Parameters
| Parameter | Type | Required | Description |
| ------------------- | -------------- | -------- | -------------------------------------------- |
| `params.encryptKey` | string | Yes | PIN to decrypt session private key |
| `params.wallet` | WalletData | Yes | User's wallet data |
| `params.session` | SessionKeyData | Yes | Session from `createSessionKey()` |
| `params.calls` | Call\[] | Yes | Array of contract calls to execute |
| `bearerToken` | string | No | JWT token (uses SDK default if not provided) |
### Return Value
Returns a `Promise` with the transaction hash.
### Example
```typescript theme={null}
// Execute transfer using session (no owner key needed!)
const txHash = await sdk.executeTransactionWithSession({
params: {
encryptKey: userPin,
wallet: { ...wallet, walletType: "CHIPI" },
session,
calls: [
{
contractAddress: USDC_CONTRACT,
entrypoint: "transfer",
calldata: [recipientAddress, amount, "0x0"],
},
],
},
bearerToken,
});
console.log(`Transfer executed: https://starkscan.co/tx/${txHash}`);
```
**Signature Format:** Session transactions use a 4-element signature `[sessionPubKey, r, s, validUntil]` instead of the owner's 2-element `[r, s]`. The SDK handles this automatically.
***
## sessions.revokeSessionKey()
Remove a session key's authorization from the [CHIPI wallet](https://github.com/chipi-pay/sessions-smart-contract) contract.
### Usage
```typescript theme={null}
const txHash = await sdk.sessions.revokeSessionKey({
encryptKey: "user-secure-pin",
wallet: {
publicKey: "0x...",
encryptedPrivateKey: "...",
walletType: "CHIPI",
},
sessionPublicKey: session.publicKey,
}, bearerToken);
```
### Parameters
| Parameter | Type | Required | Description |
| ------------------ | ---------- | -------- | --------------------------------------------- |
| `encryptKey` | string | Yes | PIN to decrypt owner's private key |
| `wallet` | WalletData | Yes | User's wallet data with `walletType: "CHIPI"` |
| `sessionPublicKey` | string | Yes | Public key of session to revoke |
| `bearerToken` | string | Yes | Valid JWT from your auth provider |
### Return Value
Returns a `Promise` with the transaction hash.
### Example
```typescript theme={null}
// Revoke session on logout
const handleLogout = async () => {
const sessionStr = localStorage.getItem('chipiSession');
if (sessionStr) {
const session = JSON.parse(sessionStr);
await sdk.sessions.revokeSessionKey({
encryptKey: userPin,
wallet: { ...wallet, walletType: "CHIPI" },
sessionPublicKey: session.publicKey,
}, bearerToken);
localStorage.removeItem('chipiSession');
}
await authProvider.signOut();
};
```
Always revoke sessions on logout to prevent unauthorized access.
***
## sessions.getSessionData()
Query the on-chain status of a session key.
### Usage
```typescript theme={null}
const sessionData = await sdk.sessions.getSessionData({
walletAddress: "0x...",
sessionPublicKey: "0x...",
});
```
### Parameters
| Parameter | Type | Required | Description |
| ------------------ | ------ | -------- | -------------------------------------------------------------------------------- |
| `walletAddress` | string | Yes | The [CHIPI wallet](https://github.com/chipi-pay/sessions-smart-contract) address |
| `sessionPublicKey` | string | Yes | Public key of the session to query |
### Return Value
Returns a `Promise`:
```typescript theme={null}
{
isActive: boolean; // true if session is valid and not revoked
validUntil: number; // Unix timestamp of expiration
remainingCalls: number; // Transactions remaining before limit
allowedEntrypoints: string[]; // Whitelisted function selectors
}
```
### Example
```typescript theme={null}
// Check session before executing
const sessionData = await sdk.sessions.getSessionData({
walletAddress: wallet.publicKey,
sessionPublicKey: session.publicKey,
});
if (!sessionData.isActive) {
console.log("Session expired or revoked - need to create new session");
return;
}
if (sessionData.remainingCalls < 5) {
console.log(`Warning: Only ${sessionData.remainingCalls} calls remaining`);
}
// Safe to proceed with transaction
await sdk.executeTransactionWithSession({ ... });
```
Use `getSessionData()` to check remaining calls before batch operations.
***
## Error Handling
All session methods throw `ChipiSessionError` for session-specific failures:
```typescript theme={null}
import { ChipiSessionError } from "@chipi-stack/backend";
try {
await sdk.sessions.addSessionKeyToContract({ ... }, bearerToken);
} catch (error) {
if (error instanceof ChipiSessionError) {
switch (error.code) {
case "INVALID_WALLET_TYPE_FOR_SESSION":
console.error("Session keys only work with CHIPI wallets");
break;
case "SESSION_EXPIRED":
console.error("Session has expired");
break;
case "SESSION_DECRYPTION_FAILED":
console.error("Invalid PIN - could not decrypt session key");
break;
default:
console.error("Session error:", error.message);
}
}
throw error;
}
```
### Error Codes
| Code | Description |
| --------------------------------- | ------------------------------------------------------------------------------------ |
| `INVALID_WALLET_TYPE_FOR_SESSION` | Wallet is not a [CHIPI wallet](https://github.com/chipi-pay/sessions-smart-contract) |
| `SESSION_EXPIRED` | Session `validUntil` has passed |
| `SESSION_NOT_REGISTERED` | Session not found on contract |
| `SESSION_REVOKED` | Session was explicitly revoked |
| `SESSION_MAX_CALLS_EXCEEDED` | No remaining calls |
| `SESSION_ENTRYPOINT_NOT_ALLOWED` | Called function not in whitelist |
| `SESSION_DECRYPTION_FAILED` | Invalid PIN for decryption |
| `SESSION_CREATION_FAILED` | Failed to generate keypair |
***
## Related
* [Session Keys Quickstart](/sdk/backend/sessions-quickstart) - Full lifecycle guide
* [CHIPI Wallet Smart Contract](https://github.com/chipi-pay/sessions-smart-contract) - Cairo implementation
* [Transfer](/sdk/backend/methods/transfer) - Basic token transfers
* [Call Any Contract](/sdk/backend/methods/call-any-contract) - Custom contract interactions
# Spending Policy Methods
Source: https://docs.chipipay.com/sdk/backend/methods/spending-policies
API reference for spending policy management — set, query, and remove per-token spending caps on session keys
Spending policies enforce per-token limits on ERC-20 operations executed by session keys. The CHIPI wallet contract enforces these automatically during transaction execution.
Requires **CHIPI v33** wallets. See the [Spending Policies guide](/sdk/guides/spending-policies) for concepts and use cases.
***
**SHHH wallets supported (14.10.0):** `setSpendingPolicy` / `removeSpendingPolicy`
now accept `walletType: "SHHH"` (V8.4) in addition to CHIPI — both embed the same
SNIP-163 session-key + spending-policy component. `undefined` still defaults to
CHIPI; READY remains unsupported (no sessions component). Proven on mainnet:
in-cap approve succeeds, over-cap reverts with `'Spending: exceeds per-call'`.
## sessions.setSpendingPolicy()
Set a spending policy for a session key + token pair. Requires owner signature.
### Usage
```typescript theme={null}
const txHash = await sdk.sessions.setSpendingPolicy({
encryptKey: "user-secure-pin",
wallet: userWallet,
spendingPolicyConfig: {
sessionPublicKey: session.publicKey,
token: "0x033068f6539f8e6e6b131e6b2b814e6c34a5224bc66947c47dab9dfee93b35fb",
maxPerCall: 1_000_000n,
maxPerWindow: 50_000_000n,
windowSeconds: 86400,
},
}, bearerToken);
```
### Parameters
| Parameter | Type | Required | Description |
| --------------------------------------- | ---------- | -------- | ---------------------------------------------- |
| `encryptKey` | string | Yes | PIN to decrypt owner's private key |
| `wallet` | WalletData | Yes | Wallet data with encrypted private key |
| `spendingPolicyConfig.sessionPublicKey` | string | Yes | Session key to apply the policy to |
| `spendingPolicyConfig.token` | string | Yes | ERC-20 token contract address |
| `spendingPolicyConfig.maxPerCall` | bigint | Yes | Max amount per single call (u256) |
| `spendingPolicyConfig.maxPerWindow` | bigint | Yes | Max cumulative amount in rolling window (u256) |
| `spendingPolicyConfig.windowSeconds` | number | Yes | Rolling window duration in seconds (u64) |
### Return Value
`Promise` — Transaction hash of the on-chain `set_spending_policy` call.
***
## sessions.getSpendingPolicy()
Query a spending policy from the contract. Read-only, no signature or gas required.
### Usage
```typescript theme={null}
const policy = await sdk.sessions.getSpendingPolicy({
walletAddress: "0x04abc...def",
sessionPublicKey: session.publicKey,
token: "0x033068f6539f8e6e6b131e6b2b814e6c34a5224bc66947c47dab9dfee93b35fb",
});
```
### Parameters
| Parameter | Type | Required | Description |
| ------------------ | ------ | -------- | ----------------------------- |
| `walletAddress` | string | Yes | Wallet contract address |
| `sessionPublicKey` | string | Yes | Session public key |
| `token` | string | Yes | ERC-20 token contract address |
### Return Value
`SpendingPolicyData`:
| Field | Type | Description |
| --------------- | ------ | ---------------------------------------------- |
| `maxPerCall` | bigint | Maximum amount per single call |
| `maxPerWindow` | bigint | Maximum cumulative amount in rolling window |
| `windowSeconds` | number | Rolling window duration in seconds |
| `spentInWindow` | bigint | Amount spent in the current active window |
| `windowStart` | number | Unix timestamp when the current window started |
Returns all zeros if no policy is set (meaning no enforcement).
***
## sessions.removeSpendingPolicy()
Remove a spending policy. After removal, the session has no limits for this token. Requires owner signature.
### Usage
```typescript theme={null}
const txHash = await sdk.sessions.removeSpendingPolicy({
encryptKey: "user-secure-pin",
wallet: userWallet,
sessionPublicKey: session.publicKey,
token: "0x033068f6539f8e6e6b131e6b2b814e6c34a5224bc66947c47dab9dfee93b35fb",
}, bearerToken);
```
### Parameters
| Parameter | Type | Required | Description |
| ------------------ | ---------- | -------- | -------------------------------------- |
| `encryptKey` | string | Yes | PIN to decrypt owner's private key |
| `wallet` | WalletData | Yes | Wallet data with encrypted private key |
| `sessionPublicKey` | string | Yes | Session key to remove policy from |
| `token` | string | Yes | ERC-20 token contract address |
### Return Value
`Promise` — Transaction hash of the on-chain `remove_spending_policy` call.
# transfer
Source: https://docs.chipipay.com/sdk/backend/methods/transfer
Transfers tokens from a user wallet to another address. Uses Avnus gasless to cover gas fees, making transactions frictionless for your users.
## Usage
```typescript theme={null}
const transferResult = await serverClient.transfer({
params: {
encryptKey: "user-secure-pin",
wallet: {
publicKey: "0x123...yourPublicKeyHere",
encryptedPrivateKey: "encrypted:key:data"
},
amount: "100",
token: ChainToken.USDC,
recipient: "0x1234567890abcdef...",
},
});
```
### Parameters
* `encryptKey` (string): PIN used to decrypt the private key
* `wallet` (WalletData): Object with publicKey and encryptedPrivateKey
* `amount` (string | number): Transfer amount
* `token` (`ChainToken`): Token identifier. Use `ChainToken.USDC`
* `recipient` (string): Destination wallet address
* `decimals` (number, optional): Token decimals (default: 18)
### Return Value
Returns a Promise that resolves to a transaction hash string.
## Example Implementation
```typescript theme={null}
import { ChipiServerSDK, ChainToken } from "@chipi-stack/backend";
const serverClient = new ChipiServerSDK({
apiPublicKey: process.env.CHIPI_PUBLIC_KEY!,
apiSecretKey: process.env.CHIPI_SECRET_KEY!,
});
async function transferTokens(
senderWallet: { publicKey: string; encryptedPrivateKey: string },
recipientAddress: string,
amount: string,
userPin: string
) {
try {
const transferResult = await serverClient.transfer({
params: {
encryptKey: userPin,
wallet: senderWallet,
amount: amount,
token: ChainToken.USDC,
recipient: recipientAddress,
},
});
console.log('Transfer completed successfully!');
console.log('Transaction hash:', transferResult);
return transferResult;
} catch (error) {
console.error('Transfer failed:', error);
throw error;
}
}
// Usage example
async function sendUSDC(userId: string, recipientAddress: string, amount: string, pin: string) {
// First, get the user's wallet
const wallet = await serverClient.getWallet({
externalUserId: userId,
});
// Then perform the transfer
const txHash = await transferTokens(
wallet,
recipientAddress,
amount,
pin
);
console.log(`Sent ${amount} USDC to ${recipientAddress}`);
console.log(`Transaction: https://starkscan.co/tx/${txHash}`);
return txHash;
}
```
Always verify recipient addresses. Transfers on StarkNet are irreversible.
## Related Methods
* [getWallet](/sdk/backend/methods/get-wallet) - Get wallet information before transfers
* [callAnyContract](/sdk/backend/methods/call-any-contract) - For custom contract interactions
# Backend SDK Quickstart
Source: https://docs.chipipay.com/sdk/backend/quickstart
Quick setup guide for the Chipi Backend SDK - server-side wallet management and transactions
Since this is a server-side SDK, the wallets are not self-custodial.
**PIN is weak — not recommended for production.**
A user-typed PIN is a short, low-entropy string. Anyone who shoulder-surfs the PIN, observes a phishing form, or compromises the browser at typing time can decrypt the wallet's private key. PIN remains in the SDK only as a fallback recovery surface for users who lose access to their platform authenticator.
**Production embedded-wallet apps should default to a platform passkey** (Touch ID, Face ID, Windows Hello, Android biometrics) via the `@chipi-stack/chipi-passkey` package. For SHHH V8.4 wallets, `signerKind: "WEBAUTHN_P256"` keeps the private key inside the platform authenticator — it never leaves the device, never reaches Chipi servers, and is never derived from a user-typed secret.
Only prompt for a PIN as the encryption key when:
* The user explicitly opted into a PIN-only flow (e.g. cold-storage / paper-backup recovery), or
* The platform genuinely has no WebAuthn / biometric support available.
If you are migrating an existing PIN-based wallet to a passkey, look up `useMigrateWalletToPasskey` in your framework's hook docs.
```bash npm theme={null}
npm install @chipi-stack/backend
```
```bash yarn theme={null}
yarn add @chipi-stack/backend
```
```bash pnpm theme={null}
pnpm add @chipi-stack/backend
```
1. Go to your [API Keys](https://dashboard.chipipay.com/configure/api-keys) in the Chipi Dashboard
2. Copy your **Public Key** (`pk_prod_xxxx`) and **Secret Key** (`sk_prod_xxxx`)
Keep your Secret Key secure and never expose it in client-side code or version control.
Create a new instance of the ChipiServerSDK with your API keys:
```typescript theme={null}
import { ChipiServerSDK, Chain, ChainToken } from "@chipi-stack/backend";
const serverClient = new ChipiServerSDK({
apiPublicKey: "pk_prod_your_public_key",
apiSecretKey: "sk_prod_your_secret_key",
});
```
Now you can create a wallet for your users:
```typescript theme={null}
const newWallet = await serverClient.createWallet({
params: {
encryptKey: "user-secure-pin",
externalUserId: "your-user-id-123",
chain: Chain.STARKNET,
},
});
console.log('New wallet created:', newWallet);
// Output: { publicKey: "0x...", encryptedPrivateKey: "...", walletType: "CHIPI", ... }
```
Transfer tokens between wallets:
```typescript theme={null}
const transferResult = await serverClient.transfer({
params: {
encryptKey: "user-secure-pin",
wallet: {
publicKey: newWallet.publicKey,
encryptedPrivateKey: newWallet.encryptedPrivateKey,
},
amount: "100",
token: ChainToken.USDC,
recipient: "0x1234567890abcdef...",
},
});
console.log('Transfer completed:', transferResult);
```
For production applications, store your API keys as environment variables:
```bash theme={null}
# .env
CHIPI_PUBLIC_KEY=pk_prod_your_public_key
CHIPI_SECRET_KEY=sk_prod_your_secret_key
```
Then initialize the SDK:
```typescript theme={null}
const serverClient = new ChipiServerSDK({
apiPublicKey: process.env.CHIPI_PUBLIC_KEY!,
apiSecretKey: process.env.CHIPI_SECRET_KEY!,
});
```
## Next Steps
Now that you have the basic setup working, explore more advanced features:
* **[Session Keys](/sdk/backend/sessions-quickstart)** - Enable delegated transaction execution
* **[API Reference](/sdk/api/introduction)** - Full API documentation
## Security Best Practices
* Never expose your secret API key in client-side code
* Use environment variables for API keys in production
* Validate user inputs before making API calls
* Implement proper error handling and logging
Need help? Join our [Telegram Community](https://t.me/+e2qjHEOwImkyZDVh) for support and to connect with other developers.
# Session Keys Quickstart
Source: https://docs.chipipay.com/sdk/backend/sessions-quickstart
Enable temporary, delegated transaction execution without requiring the owner's private key for each operation
**PIN is weak — not recommended for production.**
A user-typed PIN is a short, low-entropy string. Anyone who shoulder-surfs the PIN, observes a phishing form, or compromises the browser at typing time can decrypt the wallet's private key. PIN remains in the SDK only as a fallback recovery surface for users who lose access to their platform authenticator.
**Production embedded-wallet apps should default to a platform passkey** (Touch ID, Face ID, Windows Hello, Android biometrics) via the `@chipi-stack/chipi-passkey` package. For SHHH V8.4 wallets, `signerKind: "WEBAUTHN_P256"` keeps the private key inside the platform authenticator — it never leaves the device, never reaches Chipi servers, and is never derived from a user-typed secret.
Only prompt for a PIN as the encryption key when:
* The user explicitly opted into a PIN-only flow (e.g. cold-storage / paper-backup recovery), or
* The platform genuinely has no WebAuthn / biometric support available.
If you are migrating an existing PIN-based wallet to a passkey, look up `useMigrateWalletToPasskey` in your framework's hook docs.
## What are Session Keys?
Session keys allow your application to execute transactions on behalf of a user without requiring their owner private key for every action. Instead, a temporary keypair is created and registered on-chain with specific permissions (time limit, call limit, allowed functions).
This enables **gasless, frictionless UX** for gaming, DeFi automation, social apps, and more.
Session keys only work with [CHIPI wallets](https://github.com/chipi-pay/sessions-smart-contract) - OpenZeppelin accounts extended with SNIP-9 session key support.
## When to Use Session Keys
| Use Case | Why Sessions Help |
| ------------------- | ------------------------------------------------- |
| **Gaming** | Players make moves without wallet popups |
| **DeFi Automation** | Yield optimizers execute strategies automatically |
| **Social dApps** | Post, like, comment without friction |
| **Subscriptions** | Recurring payments without manual approval |
| **Airdrops** | Zero-friction claiming for users |
## Requirements
* A [CHIPI wallet](https://github.com/chipi-pay/sessions-smart-contract) (not READY)
* Valid bearer token from your auth provider
* User's `encryptKey` (PIN) for signing
***
```bash npm theme={null}
npm install @chipi-stack/backend
```
```bash yarn theme={null}
yarn add @chipi-stack/backend
```
```bash pnpm theme={null}
pnpm add @chipi-stack/backend
```
```typescript theme={null}
import { ChipiSDK } from "@chipi-stack/backend";
const sdk = new ChipiSDK({
apiPublicKey: process.env.CHIPI_PUBLIC_KEY!,
apiSecretKey: process.env.CHIPI_SECRET_KEY!,
});
```
Generate a session keypair locally. The SDK returns the data but does **NOT** store it - you must persist it on the client side.
```typescript theme={null}
// Create session key (self-custodial - store in client localStorage)
const session = sdk.sessions.createSessionKey({
encryptKey: "user-secure-pin",
durationSeconds: 3600, // 1 hour (default: 6 hours)
});
console.log(session);
// {
// publicKey: "0x...", // Session public key
// encryptedPrivateKey: "...", // AES encrypted with encryptKey
// validUntil: 1702500000 // Unix timestamp (seconds)
// }
// Store in client-side secure storage (NOT your database!)
localStorage.setItem('chipiSession', JSON.stringify(session));
```
Never store session keys in your backend database. Store only in client-side secure storage (localStorage, Secure Enclave, Keychain).
Before using a session key, you must register it on the [CHIPI wallet](https://github.com/chipi-pay/sessions-smart-contract) smart contract. This requires the owner's signature (one-time setup).
```typescript theme={null}
// Get user's wallet
const wallet = await sdk.getWallet({ externalUserId: "user-123" });
// Register session on contract (uses owner signature via paymaster)
const txHash = await sdk.sessions.addSessionKeyToContract({
encryptKey: "user-secure-pin",
wallet: {
publicKey: wallet.publicKey,
encryptedPrivateKey: wallet.encryptedPrivateKey,
walletType: "CHIPI",
},
sessionConfig: {
sessionPublicKey: session.publicKey,
validUntil: session.validUntil,
maxCalls: 100, // Max transactions allowed
allowedEntrypoints: [], // Empty = all functions allowed
},
}, bearerToken);
console.log("Session registered:", txHash);
```
### Session Configuration Options
| Parameter | Type | Description |
| -------------------- | --------- | ------------------------------------------------------- |
| `sessionPublicKey` | string | Public key from `createSessionKey()` |
| `validUntil` | number | Unix timestamp when session expires |
| `maxCalls` | number | Maximum transactions allowed (decrements with each use) |
| `allowedEntrypoints` | string\[] | Whitelisted function selectors. Empty = all allowed |
For security, whitelist specific entrypoints when possible:
```typescript theme={null}
allowedEntrypoints: [
"0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e", // transfer
"0x219209e083275171774dab1df80982e9df2096516f06319c5c6d71ae0a8480c", // approve
]
```
Now you can execute transactions using the session key - **no owner private key needed!**
```typescript theme={null}
// Retrieve session from client storage
const session = JSON.parse(localStorage.getItem('chipiSession')!);
// Execute transaction with session signature (4-element format)
const txHash = await sdk.executeTransactionWithSession({
params: {
encryptKey: "user-secure-pin",
wallet: {
publicKey: wallet.publicKey,
encryptedPrivateKey: wallet.encryptedPrivateKey,
walletType: "CHIPI",
},
session,
calls: [
{
contractAddress: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
entrypoint: "transfer",
calldata: [recipientAddress, amount, "0x0"],
},
],
},
bearerToken,
});
console.log("Transaction executed:", txHash);
```
**Signature Format Difference:**
* Owner signature: `[r, s]` (2 elements)
* Session signature: `[sessionPubKey, r, s, validUntil]` (4 elements)
The SDK handles this automatically.
Check if a session is still active and how many calls remain:
```typescript theme={null}
const sessionData = await sdk.sessions.getSessionData({
walletAddress: wallet.publicKey,
sessionPublicKey: session.publicKey,
});
console.log(sessionData);
// {
// isActive: true,
// validUntil: 1702500000,
// remainingCalls: 95,
// allowedEntrypoints: []
// }
if (sessionData.isActive) {
console.log(`Session has ${sessionData.remainingCalls} calls remaining`);
}
```
Always revoke sessions when no longer needed or when the user logs out:
```typescript theme={null}
const revokeTxHash = await sdk.sessions.revokeSessionKey({
encryptKey: "user-secure-pin",
wallet: {
publicKey: wallet.publicKey,
encryptedPrivateKey: wallet.encryptedPrivateKey,
walletType: "CHIPI",
},
sessionPublicKey: session.publicKey,
}, bearerToken);
// Clear from client storage
localStorage.removeItem('chipiSession');
console.log("Session revoked:", revokeTxHash);
```
Always revoke sessions on logout to prevent unauthorized access.
***
## Security and Best Practices
### Storage Guidelines
**Never store session keys in your backend database.** This defeats the purpose of self-custodial security.
Store session keys only in client-side secure storage:
| Platform | Recommended Storage |
| ---------------- | ---------------------------------------------- |
| **Web** | `localStorage` or `sessionStorage` |
| **iOS** | Secure Enclave / Keychain |
| **Android** | Android Keystore |
| **React Native** | `expo-secure-store` or `react-native-keychain` |
Use `sessionStorage` instead of `localStorage` for sessions that should expire when the browser tab closes.
### Lifecycle Best Practices
1. **Always revoke on logout** - Never leave active sessions when user signs out
2. **Set short durations** - Use 1-6 hours, not days
3. **Limit `maxCalls`** - Set realistic limits based on expected usage
4. **Whitelist entrypoints** - Restrict to only the functions your app needs
### Example: Secure Logout Handler
```typescript theme={null}
const handleLogout = async () => {
// Retrieve active session
const sessionStr = localStorage.getItem('chipiSession');
if (sessionStr) {
const session = JSON.parse(sessionStr);
try {
// Revoke session on-chain before logout
await sdk.sessions.revokeSessionKey({
encryptKey: userPin,
wallet,
sessionPublicKey: session.publicKey,
}, bearerToken);
} catch (error) {
console.warn("Failed to revoke session:", error);
// Continue with logout even if revoke fails
}
// Clear from local storage
localStorage.removeItem('chipiSession');
}
// Proceed with auth logout
await authProvider.signOut();
};
```
### Example: Session with Restricted Permissions
```typescript theme={null}
// Create a session that can ONLY transfer USDC
const restrictedSession = await sdk.sessions.addSessionKeyToContract({
encryptKey: userPin,
wallet,
sessionConfig: {
sessionPublicKey: session.publicKey,
validUntil: session.validUntil,
maxCalls: 10, // Only 10 transfers allowed
allowedEntrypoints: [
"0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e", // transfer
],
},
}, bearerToken);
```
***
## Full Example: Gaming Session
```typescript theme={null}
import { ChipiSDK } from "@chipi-stack/backend";
const sdk = new ChipiSDK({
apiPublicKey: process.env.CHIPI_PUBLIC_KEY!,
apiSecretKey: process.env.CHIPI_SECRET_KEY!,
});
// Game constants
const GAME_CONTRACT = "0x...";
const MAKE_MOVE_SELECTOR = "0x...";
const CLAIM_REWARD_SELECTOR = "0x...";
async function startGameSession(userId: string, userPin: string, bearerToken: string) {
// Get player's CHIPI wallet
const wallet = await sdk.getWallet({ externalUserId: userId });
// Create 24-hour gaming session
const session = sdk.sessions.createSessionKey({
encryptKey: userPin,
durationSeconds: 24 * 60 * 60, // 24 hours
});
// Register with game-only permissions
await sdk.sessions.addSessionKeyToContract({
encryptKey: userPin,
wallet: { ...wallet, walletType: "CHIPI" },
sessionConfig: {
sessionPublicKey: session.publicKey,
validUntil: session.validUntil,
maxCalls: 1000, // Many moves allowed
allowedEntrypoints: [MAKE_MOVE_SELECTOR, CLAIM_REWARD_SELECTOR],
},
}, bearerToken);
// Store session in client
localStorage.setItem('gameSession', JSON.stringify(session));
return session;
}
async function makeMove(moveData: string, bearerToken: string) {
const session = JSON.parse(localStorage.getItem('gameSession')!);
const wallet = await sdk.getWallet({ externalUserId: currentUserId });
// Execute move instantly - no wallet popup!
return sdk.executeTransactionWithSession({
params: {
encryptKey: userPin,
wallet: { ...wallet, walletType: "CHIPI" },
session,
calls: [{
contractAddress: GAME_CONTRACT,
entrypoint: "make_move",
calldata: [moveData],
}],
},
bearerToken,
});
}
```
***
## Next Steps
* **[Session Methods Reference](/sdk/backend/methods/sessions)** - Detailed API for each session method
* **[CHIPI Wallet Smart Contract](https://github.com/chipi-pay/sessions-smart-contract)** - Cairo implementation details
* **[Transfer Tokens](/sdk/backend/methods/transfer)** - Basic token transfers
Need help? Join our [Telegram Community](https://t.me/+e2qjHEOwImkyZDVh) for support.
# Amount
Source: https://docs.chipipay.com/sdk/core/amount
Immutable token amount class with safe BigInt arithmetic, parsing, and formatting.
`@chipi-stack/core` is in development (v0.1.0). APIs may change before stable release.
## Overview
`Amount` is an immutable value type that represents a token amount using `BigInt` internally. All arithmetic is exact with zero floating-point precision loss.
## Usage
```typescript theme={null}
import { Amount } from "@chipi-stack/core";
const usdc = { symbol: "USDC", decimals: 6, address: "0x..." };
// Parse human-readable string
const amount = Amount.parse("1.5", usdc);
amount.toBase(); // 1_500_000n
amount.toUnit(); // "1.5"
amount.toFormatted(); // "1.5 USDC"
```
## Factory Methods
### `Amount.parse(value, token)`
Parse a human-readable string into an Amount.
```typescript theme={null}
const amount = Amount.parse("100.50", { symbol: "USDC", decimals: 6, address: "0x..." });
```
#### Parameters
* `value` (string): Human-readable value (e.g., `"1.5"`, `"100"`)
* `token` (TokenInfo): Object with `symbol`, `decimals`, and `address`
#### Throws
* If `value` is negative or non-numeric
* If `value` has more decimal places than the token supports
### `Amount.fromRaw(raw, decimals, symbol?)`
Create an Amount from a raw bigint value (base units).
```typescript theme={null}
const amount = Amount.fromRaw(1_500_000n, 6, "USDC");
amount.toUnit(); // "1.5"
```
#### Parameters
* `raw` (bigint): Raw value in base units (e.g., wei)
* `decimals` (number): Token decimal places
* `symbol` (string, optional): Token symbol, defaults to `""`
#### Throws
* If `raw` is negative
## Arithmetic
All arithmetic methods return a new `Amount` (immutable). Both operands must have the same `decimals`.
```typescript theme={null}
const a = Amount.parse("10.0", usdc);
const b = Amount.parse("3.5", usdc);
a.add(b); // 13.5 USDC
a.subtract(b); // 6.5 USDC
a.multiply(2n); // 20.0 USDC
a.divide(4n); // 2.5 USDC (truncates, no rounding)
```
### Methods
| Method | Signature | Description |
| ---------- | ---------------------------------- | --------------------------------------------- |
| `add` | `add(other: Amount): Amount` | Add two amounts |
| `subtract` | `subtract(other: Amount): Amount` | Subtract (throws if result would be negative) |
| `multiply` | `multiply(scalar: bigint): Amount` | Multiply by a scalar |
| `divide` | `divide(divisor: bigint): Amount` | Divide by a scalar (truncates) |
## Comparisons
```typescript theme={null}
const a = Amount.parse("10.0", usdc);
const b = Amount.parse("3.5", usdc);
a.gt(b); // true
a.lt(b); // false
a.eq(b); // false
a.gte(b); // true
a.lte(b); // false
a.isZero(); // false
```
### Methods
| Method | Signature | Description |
| -------- | ----------------------------- | ----------------------- |
| `eq` | `eq(other: Amount): boolean` | Equal |
| `gt` | `gt(other: Amount): boolean` | Greater than |
| `gte` | `gte(other: Amount): boolean` | Greater than or equal |
| `lt` | `lt(other: Amount): boolean` | Less than |
| `lte` | `lte(other: Amount): boolean` | Less than or equal |
| `isZero` | `isZero(): boolean` | Check if amount is zero |
## Formatting
```typescript theme={null}
const amount = Amount.parse("1500000.0", usdc);
amount.toBase(); // 1_500_000_000_000n (raw bigint)
amount.toUnit(); // "1500000.0"
amount.toFormatted(); // "1500000.0 USDC"
amount.toFormatted(true); // "1.5M USDC" (compressed)
```
### Methods
| Method | Signature | Description |
| ------------- | ------------------------------------------- | ------------------------------------------------- |
| `toBase` | `toBase(): bigint` | Raw value in base units |
| `toUnit` | `toUnit(): string` | Human-readable string (e.g., `"1.5"`) |
| `toFormatted` | `toFormatted(compressed?: boolean): string` | Formatted with symbol, optional compression (K/M) |
## Properties
| Property | Type | Description |
| ---------- | -------- | ---------------------------------- |
| `raw` | `bigint` | Raw value in base units (readonly) |
| `decimals` | `number` | Token decimal places (readonly) |
| `symbol` | `string` | Token symbol (readonly) |
## Example
```typescript theme={null}
import { Amount, TokenRegistry } from "@chipi-stack/core";
const registry = new TokenRegistry("mainnet");
const usdcInfo = registry.bySymbol("USDC")!;
// Parse user input
const sendAmount = Amount.parse("25.50", usdcInfo);
// Get balance (returned by Erc20.balanceOf())
const balance = Amount.fromRaw(50_000_000n, 6, "USDC");
// Compare
if (balance.lt(sendAmount)) {
console.log(`Insufficient: have ${balance.toFormatted()}, need ${sendAmount.toFormatted()}`);
} else {
const remaining = balance.subtract(sendAmount);
console.log(`After transfer: ${remaining.toFormatted()}`); // "24.5 USDC"
}
```
## Related
* [TxBuilder](/sdk/core/tx-builder): Use Amount with TxBuilder for typed transfers
* [Erc20](/sdk/core/erc20): Returns Amount objects from `balanceOf()` and `allowance()`
* [TokenRegistry](/sdk/core/token-registry): Look up TokenInfo for Amount.parse()
# Erc20
Source: https://docs.chipipay.com/sdk/core/erc20
Typed ERC20 contract wrapper that returns Amount objects for balances and allowances.
`@chipi-stack/core` is in development (v0.1.0). APIs may change before stable release.
## Overview
`Erc20` provides a typed wrapper around ERC20 contract interactions on StarkNet. Read methods (`balanceOf`, `allowance`) return `Amount` objects. Write methods (`approveCall`, `transferCall`) return `Call` objects for use with `TxBuilder` or `Account.execute()`.
## Usage
```typescript theme={null}
import { Erc20, Amount, TokenRegistry } from "@chipi-stack/core";
const registry = new TokenRegistry("mainnet");
const usdcInfo = registry.bySymbol("USDC")!;
const usdc = new Erc20(usdcInfo, account);
// Query balance (returns Amount)
const balance = await usdc.balanceOf(myAddress);
console.log(balance.toFormatted()); // "150.5 USDC"
```
## Constructor
```typescript theme={null}
new Erc20(token: TokenInfo, account: Account)
```
### Parameters
* `token` (TokenInfo): Object with `symbol`, `decimals`, and `address`
* `account` (Account): A starknet.js `Account` instance
## Methods
### `balanceOf(address)`
Query the ERC20 balance of an address.
```typescript theme={null}
const balance = await usdc.balanceOf("0x123...");
console.log(balance.toFormatted()); // "1500.0 USDC"
console.log(balance.toBase()); // 1500000000n
```
#### Parameters
* `address` (string): StarkNet address to query
#### Returns
`Promise`: The balance as an Amount object
### `allowance(owner, spender)`
Query the allowance granted by `owner` to `spender`.
```typescript theme={null}
const allowed = await usdc.allowance(ownerAddress, spenderAddress);
if (allowed.lt(transferAmount)) {
console.log("Need to approve more tokens");
}
```
#### Parameters
* `owner` (string): Token owner address
* `spender` (string): Approved spender address
#### Returns
`Promise`: The allowance as an Amount object
### `approveCall(spender, amount)`
Build an ERC20 approve Call object. Does not execute the transaction.
```typescript theme={null}
const call = usdc.approveCall(spenderAddress, amount);
// Use with TxBuilder
await new TxBuilder(account).add(call).send();
```
#### Parameters
* `spender` (string): Address to approve
* `amount` (Amount): Amount to approve
#### Returns
`Call`: A StarkNet Call object for use with TxBuilder or Account.execute()
#### Throws
* If `amount.decimals` doesn't match the token's decimals
* If `amount.symbol` doesn't match the token's symbol (when set)
### `transferCall(recipient, amount)`
Build an ERC20 transfer Call object. Does not execute the transaction.
```typescript theme={null}
const call = usdc.transferCall(recipientAddress, amount);
await new TxBuilder(account).add(call).send();
```
#### Parameters
* `recipient` (string): Destination address
* `amount` (Amount): Amount to transfer
#### Returns
`Call`: A StarkNet Call object
#### Throws
* If `amount.decimals` doesn't match the token's decimals
* If `amount.symbol` doesn't match the token's symbol (when set)
## Example
### Full Flow: Check Balance, Approve, Transfer
```typescript theme={null}
import { Erc20, Amount, TxBuilder, TokenRegistry } from "@chipi-stack/core";
const registry = new TokenRegistry("mainnet");
const usdcInfo = registry.bySymbol("USDC")!;
const usdc = new Erc20(usdcInfo, account);
const sendAmount = Amount.parse("25.0", usdcInfo);
// Check balance
const balance = await usdc.balanceOf(myAddress);
if (balance.lt(sendAmount)) {
throw new Error(`Insufficient: ${balance.toFormatted()} < ${sendAmount.toFormatted()}`);
}
// Check allowance
const allowed = await usdc.allowance(myAddress, dexAddress);
// Build atomic transaction
const builder = new TxBuilder(account);
if (allowed.lt(sendAmount)) {
builder.add(usdc.approveCall(dexAddress, sendAmount));
}
builder.add(usdc.transferCall(recipientAddress, sendAmount));
// Preview fee, then send
const fee = await builder.estimateFee();
console.log(`Fee: ${fee.overall_fee} wei`);
const result = await new TxBuilder(account)
.add(usdc.approveCall(dexAddress, sendAmount))
.add(usdc.transferCall(recipientAddress, sendAmount))
.send();
```
## Related
* [Amount](/sdk/core/amount): The value type returned by `balanceOf()` and `allowance()`
* [TxBuilder](/sdk/core/tx-builder): Execute Call objects in atomic batches
* [TokenRegistry](/sdk/core/token-registry): Look up TokenInfo for the Erc20 constructor
# Core SDK
Source: https://docs.chipipay.com/sdk/core/introduction
Transaction primitives for the Chipi SDK ecosystem: Amount, TxBuilder, ERC20, TokenRegistry, and SignerAdapter.
`@chipi-stack/core` is in development (v0.1.0). APIs may change before stable release.
## What is @chipi-stack/core?
`@chipi-stack/core` provides opt-in transaction primitives for developers who need fine-grained control over StarkNet operations. It complements the existing hooks-based SDKs (Next.js, React, Expo) with lower-level building blocks.
### When to use core vs hooks
| Use Case | Recommended | Why |
| --------------------------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------ |
| Standard transfers in a React app | Hooks (`useTransfer`) | Simpler, handles state management |
| Batch approve + transfer atomically | **Core** (`TxBuilder`) | Hooks don't support multicall batching |
| Display formatted balances | Either | Core's `Amount` class or shared's `formatAmount` |
| Custom contract interactions with fee preview | **Core** (`TxBuilder.estimateFee()`) | Hooks don't expose fee estimation |
| Backend automation with programmatic signing | **Core** (`DirectSigner` + `TxBuilder`) | No React context available |
| Swap passkey for Argent/Braavos wallet signer | **Core** (`ExternalSigner`) | Hooks are hardwired to passkey flow |
| Gasless transactions with any wallet (Argent X, Braavos, Cartridge, DirectSigner) | **Core** (`TxBuilder.sendSponsored()`) | Uses Chipi's paymaster for gas sponsorship |
## Installation
```bash npm theme={null}
npm install @chipi-stack/core @chipi-stack/backend
```
```bash yarn theme={null}
yarn add @chipi-stack/core @chipi-stack/backend
```
```bash pnpm theme={null}
pnpm add @chipi-stack/core @chipi-stack/backend
```
## Quick Example
```typescript theme={null}
import { Amount, TxBuilder, Erc20, TokenRegistry, createAccount, DirectSigner } from "@chipi-stack/core";
import { ChipiServerSDK } from "@chipi-stack/backend";
// Create signer + account
const signer = new DirectSigner(process.env.PRIVATE_KEY!, myAddress);
const account = await createAccount({
signer,
provider: { nodeUrl: "https://starknet-mainnet.infura.io/v3/..." },
});
// Look up USDC from the built-in token registry
const registry = new TokenRegistry("mainnet");
const usdcInfo = registry.bySymbol("USDC")!;
// Parse a human-readable amount safely (no floating point)
const amount = Amount.parse("10.0", usdcInfo);
// Create a typed ERC20 wrapper
const usdc = new Erc20(usdcInfo, account);
// Check balance
const balance = await usdc.balanceOf(myAddress);
console.log(balance.toFormatted()); // "150.5 USDC"
// Gasless: approve + transfer via Chipi's paymaster
const sdk = new ChipiServerSDK({ apiPublicKey: "pk_...", apiSecretKey: "sk_..." });
const paymaster = sdk.createPaymasterAdapter();
const txHash = await new TxBuilder(account, { paymaster })
.add(usdc.approveCall(spender, amount))
.add(usdc.transferCall(recipient, amount))
.sendSponsored(); // gasless!
// Or direct (user pays gas):
const result = await new TxBuilder(account)
.add(usdc.approveCall(spender, amount))
.add(usdc.transferCall(recipient, amount))
.send();
```
## Package Architecture
```mermaid theme={null}
graph LR
TYPES["@chipi-stack/types"] --> CORE["@chipi-stack/core"]
CORE --> AMOUNT["Amount"]
CORE --> TXBUILDER["TxBuilder"]
CORE --> ERC20["Erc20"]
CORE --> REGISTRY["TokenRegistry"]
CORE --> SIGNER["SignerAdapter"]
```
`core` depends only on `@chipi-stack/types`. It does not modify or depend on `@chipi-stack/shared`, keeping the two packages independent.
## What's Inside
Immutable token amount class with BigInt arithmetic, parsing, and formatting.
Fluent builder for batching StarkNet multicalls into atomic transactions.
Typed ERC20 contract wrapper that returns Amount objects.
Token metadata lookup by symbol or address, with network presets.
Unified signing interface for passkeys, direct keys, and external providers.
# Privacy (STRK20 shield/unshield)
Source: https://docs.chipipay.com/sdk/core/privacy
The confidential-balance engine inside @chipi-stack/core: companion accounts, gas-tank funding, proving, and gateway submission.
**Shielding (deposits) is temporarily paused.** After StarkWare's July 2026
STRK20 pool **v2.0** upgrade, `shield` / `execShield` into the shared privacy
pool requires a screener attestation issued by StarkWare's hosted proving
service — Chipi is finalizing that access. The `additional_data` plumbing in
this release carries that attestation once available. **`unshield` and
`getPrivateBalance` work today; shields will revert until access lands.**
We'll announce on X ([@hichipipay](https://x.com/hichipipay)) when shielding
is back on.
The engine behind [Private Balances](/sdk/guides/private-balances) and the
React hooks. Use this layer directly for headless/server flows; apps should
prefer the hooks.
## The companion account
The StarkWare pool requires its `user_addr` to implement SNIP-6
`is_valid_signature`, which advanced account classes (like Chipi's SHHH
wallets) deliberately do not expose. Instead of changing an audited account
class, each user gets a **companion**: a lightweight standard account (any
SNIP-6 class, e.g. OpenZeppelin
`0x061dac032f228abef9c6626f995015233097ae253a7f72d68552db02f2971b8f`) that
acts as the pool identity.
* **Derived, not created**: `deriveCompanionKeypair(anchor)` +
`deriveCompanionAddress({publicKey, classHash})` are deterministic from the
user's owner-independent anchor. Cache the address; never the keys.
* **Counterfactual**: it exists on-chain only after first use.
`execShield` deploys it automatically (self-paid from gas-tank-fronted
STRK, salt = pubkey so the deployed address equals the derived one).
* **Never holds user funds at rest**: deposits pass through it into the
pool; withdrawals pass back out to the user's real wallet; leftover
fronted STRK sweeps back to the gas tank.
Why this design over changing the account class: see our write-up
[Adding confidential payments to an advanced smart account — two ways to use
the StarkWare privacy pool, and why we picked the companion](https://community.starknet.io/t/adding-confidential-payments-to-an-advanced-smart-account-two-ways-to-use-the-starkware-privacy-pool-and-why-we-picked-the-companion/).
## Headless example
```ts theme={null}
import {
ChipiProofProvider,
derivePoolKeypair,
deriveCompanionKeypair,
deriveCompanionAddress,
requestGasFunding,
waitForFunding,
execShield,
GAS_FUND_STRK,
} from "@chipi-stack/core";
import { RpcProvider } from "starknet";
const pool = await derivePoolKeypair({ anchorSecret, seedDomainTag: wallet.address });
const companion = await deriveCompanionKeypair(anchorSecret);
const companionAddress = deriveCompanionAddress({
publicKey: companion.publicKey,
classHash: COMPANION_CLASS_HASH,
});
const provider = new RpcProvider({ nodeUrl: "https://api.cartridge.gg/x/starknet/mainnet" });
await requestGasFunding(companionAddress, {
baseUrl: "https://api.chipipay.com/v1",
apiKey: apiPublicKey,
getBearerToken: () => getToken(),
});
await waitForFunding(provider, companionAddress, { strk: GAS_FUND_STRK });
const result = await execShield(
{
provider,
chainId: "0x534e5f4d41494e",
companionAddress,
companionKey: companion.privateKey,
classHash: COMPANION_CLASS_HASH,
poolKey: BigInt(pool.privateKey),
provingProvider: new ChipiProofProvider({
baseUrl: "https://api.chipipay.com/v1",
apiKey: apiPublicKey,
getBearerToken: () => getToken(),
chainId: "0x534e5f4d41494e",
}),
},
USDC_ADDRESS,
1_000_000n,
);
```
## Multi-token operations
### Shield several tokens at once
`execShieldMany` batches N deposits into ONE `apply_actions` — one proof, one
transaction, **one 4-STRK pool fee for the whole batch** (mainnet-proven by
the S5 smoke, `scripts/multi-token-smoke.mjs`). It takes the same
`CompanionCtx` as `execShield`:
```ts theme={null}
import {
ChipiProofProvider,
derivePoolKeypair,
deriveCompanionKeypair,
deriveCompanionAddress,
execShieldMany,
STRK_ADDRESS,
} from "@chipi-stack/core";
import { RpcProvider } from "starknet";
const pool = await derivePoolKeypair({ anchorSecret, seedDomainTag: wallet.address });
const companion = await deriveCompanionKeypair(anchorSecret);
const ctx = {
provider: new RpcProvider({ nodeUrl: "https://api.cartridge.gg/x/starknet/mainnet" }),
chainId: "0x534e5f4d41494e",
companionAddress: deriveCompanionAddress({
publicKey: companion.publicKey,
classHash: COMPANION_CLASS_HASH,
}),
companionKey: companion.privateKey,
classHash: COMPANION_CLASS_HASH,
poolKey: BigInt(pool.privateKey),
provingProvider: new ChipiProofProvider({
baseUrl: "https://api.chipipay.com/v1",
apiKey: apiPublicKey,
getBearerToken: () => getToken(),
chainId: "0x534e5f4d41494e",
}),
};
// One tx, one proof, ONE 4-STRK fee for the whole batch.
const shielded = await execShieldMany(ctx, [
{ token: USDC_ADDRESS, amount: 1_000_000n }, // 1 USDC
{ token: STRK_ADDRESS, amount: 10n ** 18n }, // 1 STRK
]);
console.log(shielded.transactionHash, shielded.via);
```
Two things `execShieldMany` handles for you:
* **Approvals are aggregated.** When STRK itself is in the batch, its deposit
approval and the pool's 4-STRK fee approval share one allowance slot —
separate approves would overwrite each other, so they are summed into a
single approval.
* **Single-item batches delegate** to the proven single-token path — calling
it with one deposit is exactly `execShield`.
Funding is the same pre-step as the single-token example above
(`requestGasFunding` + `waitForFunding`), with one addition: the companion
must hold every deposit token before the call, and when STRK is one of the
deposits it needs `GAS_FUND_STRK` **plus** the STRK being shielded — the
fronted envelope is gas/fee money, never the deposit.
### Unshielding several tokens
There is no `unshieldMany` yet: **each withdrawal is its own proof, its own
transaction, and its own 4-STRK pool fee.** Two operational constraints,
both found on mainnet by the S5 smoke:
1. **Refuel before every withdrawal.** The gateway validates \~7.8 STRK of max
resource bounds per transaction, so gas funded once runs dry after the
first withdrawal.
2. **Wait for the prover's view between operations.** Proving runs against
`head − 12`; state written moments ago is invisible to it for \~13 blocks
(\~5–6 minutes). Withdrawals share the user's note channel, so the second
compile must see the pool state the first one just changed. Submitting too
early fails with the prover's `state_too_fresh` hint
(`retry_after_seconds` in the error body) — wait and retry.
```ts theme={null}
import {
execUnshield,
getPrivateBalance,
requestGasFunding,
waitForFunding,
sweepCompanion,
GAS_FUND_STRK,
type CompanionCtx,
} from "@chipi-stack/core";
import { Account } from "starknet";
declare const ctx: CompanionCtx; // the same ctx used to shield
declare const gasTankAddress: string; // leftover fronted STRK returns here
// Exact private amounts per token (viewing-key note scan, read-only).
const account = new Account({
provider: ctx.provider,
address: ctx.companionAddress,
signer: ctx.companionKey,
cairoVersion: "1",
});
const { byToken } = await getPrivateBalance({
provider: ctx.provider,
account,
viewingKey: ctx.poolKey,
provingProvider: ctx.provingProvider,
});
// The prover proves against head − 12: wait until it can SEE the state the
// previous operation wrote before compiling the next one.
async function waitProverView(fromBlock: number) {
for (;;) {
const b = await ctx.provider.getBlockNumber();
if (b >= fromBlock + 13) return;
await new Promise((r) => setTimeout(r, 30_000));
}
}
const gasOpts = {
baseUrl: "https://api.chipipay.com/v1",
apiKey: apiPublicKey,
getBearerToken: () => getToken(),
};
// `lastOpBlock` starts at the SHIELD's block — the freshly created notes are
// just as invisible to the prover as a fresh withdrawal.
let lastOpBlock = await ctx.provider.getBlockNumber();
for (const [token, amountRaw] of Object.entries(byToken)) {
if (amountRaw === 0n) continue;
await waitProverView(lastOpBlock);
await requestGasFunding(ctx.companionAddress, gasOpts); // refuel EVERY op
await waitForFunding(ctx.provider, ctx.companionAddress, { strk: GAS_FUND_STRK });
const result = await execUnshield(ctx, token, amountRaw);
lastOpBlock = await ctx.provider.getBlockNumber();
// The sweep IS the delivery of the user's money here — strict, not
// best-effort, so a failed delivery throws instead of claiming success.
await sweepCompanion({
provider: ctx.provider,
companionAddress: ctx.companionAddress,
companionKey: ctx.companionKey,
gasTank: gasTankAddress,
deliverToken: { address: token, to: wallet.address },
strict: true,
});
console.log(`unshielded ${token}: ${result.transactionHash}`);
}
```
In an interactive app you usually won't hard-wait between withdrawals —
run them sequentially, catch the `state_too_fresh` failure, tell the user
what already came back (it IS back), and retry the remaining token a few
minutes later. Money is never stranded: a failed withdrawal leaves the notes
in the pool, spendable on the next attempt.
## Trust model
Keys derive client-side and are never stored server-side, but delegated
proving includes the pool key in each proof request: Chipi's private prover
sees it per operation. Privacy holds against the public, not the proving
operator. Details in the [guide](/sdk/guides/private-balances).
# SignerAdapter
Source: https://docs.chipipay.com/sdk/core/signer-adapter
Unified signing interface with implementations for passkeys, direct keys, and external providers.
`@chipi-stack/core` is in development (v0.1.0). APIs may change before stable release.
## Overview
`SignerAdapter` is an interface that abstracts over different key management strategies. Instead of hardwiring passkey logic into transaction flows, you program against the `SignerAdapter` interface and swap implementations as needed.
## The Interface
```typescript theme={null}
interface SignerAdapter {
getPublicKey(): Promise;
sign(messageHash: string): Promise;
getAccountAddress(): Promise;
}
interface SignResult {
r: string;
s: string;
}
```
## Implementations
### DirectSigner
Signs with a local private key. For development and testing only.
```typescript theme={null}
import { DirectSigner } from "@chipi-stack/core";
const signer = new DirectSigner(
"0x1234...privateKey",
"0xabcd...accountAddress" // optional
);
const pubKey = await signer.getPublicKey();
const sig = await signer.sign(messageHash);
```
Never use DirectSigner with real private keys in production. It exists for local development and testing.
#### Constructor
```typescript theme={null}
new DirectSigner(privateKey: string, accountAddress?: string)
```
* `privateKey` (string): Hex-encoded StarkNet private key (must start with `0x`)
* `accountAddress` (string, optional): Account address. Defaults to the derived public key.
#### Throws
* If `privateKey` is not a valid hex string starting with `0x`
### PasskeySigner
Wraps a WebAuthn assertion function that produces Starknet-compatible signatures `{r, s}` from a challenge. Users authenticate via biometrics (Face ID, fingerprint) and the result is used to sign transactions.
```typescript theme={null}
import { PasskeySigner } from "@chipi-stack/core";
// Your WebAuthn assertion function that returns a Starknet signature
async function passkeySign(challenge: string): Promise<{ r: string; s: string }> {
// Call your WebAuthn authentication flow here
// The challenge is the message hash to sign
const assertion = await navigator.credentials.get({
publicKey: { challenge: new TextEncoder().encode(challenge), /* ... */ },
});
// Extract r, s from the assertion response
return { r: "0x...", s: "0x..." };
}
const signer = new PasskeySigner({
authenticateFn: passkeySign,
publicKey: userPasskeyPublicKey,
accountAddress: userAccountAddress,
});
// Now use with TxBuilder via the Account
const sig = await signer.sign(messageHash);
```
#### Constructor
```typescript theme={null}
new PasskeySigner(opts: {
authenticateFn: (challenge: string) => Promise<{ r: string; s: string }>;
publicKey: string;
accountAddress: string;
})
```
* `authenticateFn`: A function that takes a challenge hash and returns a Starknet-compatible signature `{r, s}` via WebAuthn assertion
* `publicKey`: The passkey public key obtained during registration
* `accountAddress`: The user's StarkNet account address
Most developers should use the `usePasskeyAuth` hook from `@chipi-stack/chipi-passkey/hooks` instead of `PasskeySigner` directly. `PasskeySigner` is for advanced use cases where you need low-level control over the signing flow.
### ExternalSigner
Generic adapter for any external signing provider. You supply the three interface methods and ExternalSigner wraps them.
```typescript theme={null}
import { ExternalSigner } from "@chipi-stack/core";
// Example: Argent Web Wallet integration
const signer = new ExternalSigner({
getPublicKey: async () => argentWallet.getPublicKey(),
sign: async (hash) => {
const sig = await argentWallet.signMessage(hash);
return { r: sig.r, s: sig.s };
},
getAccountAddress: async () => argentWallet.address,
});
```
#### Constructor
```typescript theme={null}
new ExternalSigner(opts: {
getPublicKey: () => Promise;
sign: (messageHash: string) => Promise;
getAccountAddress: () => Promise;
})
```
## Use Cases
### Backend Automation
Use `DirectSigner` with a key stored in a vault/HSM for programmatic transaction execution:
```typescript theme={null}
const signer = new DirectSigner(process.env.AUTOMATION_KEY!);
// Build and sign transactions programmatically
```
### Multi-Wallet Support
Support multiple wallet providers without branching transaction code:
```typescript theme={null}
function getSignerForUser(user: User): SignerAdapter {
switch (user.walletType) {
case "passkey":
return new PasskeySigner({ ... });
case "argent":
return new ExternalSigner({ ... });
case "braavos":
return new ExternalSigner({ ... });
}
}
// Transaction code is the same regardless of signer
const signer = getSignerForUser(currentUser);
```
### Easy Testing
Mock signers for unit tests:
```typescript theme={null}
const mockSigner = new ExternalSigner({
getPublicKey: async () => "0xtest_pubkey",
sign: async (hash) => ({ r: "0x1", s: "0x2" }),
getAccountAddress: async () => "0xtest_account",
});
```
### Auth Provider Migration
If you switch from Privy to another provider, only the SignerAdapter implementation changes. All TxBuilder, Erc20, and Amount code stays the same.
## Connecting to TxBuilder
`createAccount()` bridges any `SignerAdapter` into a starknet.js `Account` for use with `TxBuilder`.
### Gasless (recommended)
```typescript theme={null}
import { createAccount, DirectSigner, TxBuilder } from "@chipi-stack/core";
import { ChipiServerSDK } from "@chipi-stack/backend";
const sdk = new ChipiServerSDK({ apiPublicKey: "pk_...", apiSecretKey: "sk_..." });
const paymaster = sdk.createPaymasterAdapter();
const provider = { nodeUrl: "https://starknet-mainnet.infura.io/v3/YOUR_KEY" };
const accountAddress = "0xYOUR_ACCOUNT_ADDRESS";
const signer = new DirectSigner(process.env.PRIVATE_KEY!, accountAddress);
const account = await createAccount({ signer, provider });
const txHash = await new TxBuilder(account, { paymaster })
.transfer(USDC, [{ to, amount }])
.sendSponsored(); // gasless!
```
### Direct (user pays gas)
```typescript theme={null}
const provider = { nodeUrl: "https://starknet-mainnet.infura.io/v3/YOUR_KEY" };
const accountAddress = "0xYOUR_ACCOUNT_ADDRESS";
const signer = new DirectSigner(process.env.PRIVATE_KEY!, accountAddress);
const account = await createAccount({ signer, provider });
const result = await new TxBuilder(account)
.transfer(USDC, [{ to, amount }])
.send(); // user pays STRK for gas
```
## Related
* [TxBuilder](/sdk/core/tx-builder): Uses the Account (which uses the signer) to execute transactions
* [Introduction](/sdk/core/introduction): Overview of when to use core vs hooks
# TokenRegistry
Source: https://docs.chipipay.com/sdk/core/token-registry
Token metadata lookup by symbol or address, with built-in presets for StarkNet mainnet and Sepolia.
`@chipi-stack/core` is in development (v0.1.0). APIs may change before stable release.
## Overview
`TokenRegistry` provides a lookup table for token metadata (symbol, decimals, address, name) on StarkNet networks. It ships with built-in presets for mainnet and Sepolia, eliminating the need to hardcode token addresses.
## Usage
```typescript theme={null}
import { TokenRegistry, Amount } from "@chipi-stack/core";
const registry = new TokenRegistry("mainnet");
const usdc = registry.bySymbol("USDC")!;
console.log(usdc.address); // "0x033068F6539..."
console.log(usdc.decimals); // 6
// Use with Amount
const amount = Amount.parse("10.0", usdc);
```
## Constructor
```typescript theme={null}
new TokenRegistry(network: "mainnet" | "sepolia")
```
### Parameters
* `network` (`"mainnet"` | `"sepolia"`): The StarkNet network to load presets for
### Throws
* If `network` is not `"mainnet"` or `"sepolia"`
## Methods
### `bySymbol(symbol)`
Find a token by its symbol (case-insensitive).
```typescript theme={null}
const eth = registry.bySymbol("ETH");
const usdc = registry.bySymbol("usdc"); // case-insensitive
```
#### Parameters
* `symbol` (string): Token symbol (e.g., `"USDC"`, `"ETH"`)
#### Returns
`TokenEntry | undefined`
### `byAddress(address)`
Find a token by its contract address (case-insensitive).
```typescript theme={null}
// Native USDC on Starknet mainnet (not USDC.e bridged)
const token = registry.byAddress("0x033068F6539f8e6e6b131e6B2B814e6c34A5224bC66947c47DaB9dFeE93b35fb");
console.log(token?.symbol); // "USDC"
```
#### Parameters
* `address` (string): Token contract address
#### Returns
`TokenEntry | undefined`
### `all()`
List all tokens in this registry.
```typescript theme={null}
const tokens = registry.all();
console.log(tokens.map(t => t.symbol)); // ["ETH", "USDC", "USDT", "DAI", "STRK", "WBTC"]
```
#### Returns
`TokenEntry[]`
## Built-in Presets
### Mainnet
| Symbol | Decimals | Name |
| ------ | -------- | --------------- |
| ETH | 18 | Ether |
| USDC | 6 | USD Coin |
| USDT | 6 | Tether USD |
| DAI | 18 | Dai Stablecoin |
| STRK | 18 | Starknet Token |
| WBTC | 8 | Wrapped Bitcoin |
### Sepolia (Testnet)
| Symbol | Decimals | Name |
| ------ | -------- | ------------------ |
| ETH | 18 | Ether |
| USDC | 6 | USD Coin (Testnet) |
| STRK | 18 | Starknet Token |
## TokenEntry Type
```typescript theme={null}
interface TokenEntry {
symbol: string; // e.g., "USDC"
decimals: number; // e.g., 6
address: string; // StarkNet contract address
name: string; // e.g., "USD Coin"
}
```
`TokenEntry` extends `TokenInfo` (used by `Amount.parse()`) with an additional `name` field.
## Example
```typescript theme={null}
import { TokenRegistry, Amount, Erc20 } from "@chipi-stack/core";
// Set up registry for the target network
const network = process.env.STARKNET_NETWORK === "mainnet" ? "mainnet" : "sepolia";
const registry = new TokenRegistry(network);
// Look up token and create typed wrapper
const usdcInfo = registry.bySymbol("USDC");
if (!usdcInfo) throw new Error("USDC not found in registry");
const usdc = new Erc20(usdcInfo, account);
const amount = Amount.parse("50.0", usdcInfo);
const balance = await usdc.balanceOf(myAddress);
console.log(`Balance: ${balance.toFormatted()}`);
```
## Related
* [Amount](/sdk/core/amount): Use TokenEntry from the registry with `Amount.parse()`
* [Erc20](/sdk/core/erc20): Pass TokenEntry to the Erc20 constructor
# TxBuilder
Source: https://docs.chipipay.com/sdk/core/tx-builder
Fluent builder for batching StarkNet multicalls into atomic transactions with fee estimation.
`@chipi-stack/core` is in development (v0.1.0). APIs may change before stable release.
## Overview
`TxBuilder` provides a fluent API for composing multiple StarkNet calls into a single atomic transaction. All calls execute together via `Account.execute()`, which is more gas-efficient than separate transactions (important when sponsoring gas via a paymaster).
## Usage
```typescript theme={null}
import { TxBuilder } from "@chipi-stack/core";
import type { Account } from "starknet";
const result = await new TxBuilder(account)
.approve(tokenAddr, spenderAddr, amount)
.transfer(tokenAddr, [{ to: recipient, amount: 1_000_000n }])
.send();
console.log(result.transaction_hash);
```
## Constructor
```typescript theme={null}
new TxBuilder(account: Account, opts?: { paymaster?: PaymasterAdapter })
```
### Parameters
* `account` (Account): A starknet.js `Account` instance
* `opts.paymaster` (PaymasterAdapter, optional): Adapter for gasless transactions via `sendSponsored()`
## Methods
### `add(call)`
Add one or more raw StarkNet calls to the batch.
```typescript theme={null}
builder.add({
contractAddress: "0x...",
entrypoint: "swap",
calldata: [tokenIn, tokenOut, amountIn, "0"],
});
// Or add multiple at once
builder.add([call1, call2, call3]);
```
#### Parameters
* `call` (Call | Call\[]): A single Call or array of Calls
#### Returns
`this` (chainable)
### `approve(token, spender, amount)`
Add an ERC20 approve call. Handles uint256 encoding automatically.
```typescript theme={null}
builder.approve(USDC_ADDRESS, DEX_ADDRESS, 1_000_000n);
```
#### Parameters
* `token` (string): Token contract address
* `spender` (string): Address to approve
* `amount` (bigint): Raw amount in base units
#### Returns
`this` (chainable)
### `transfer(token, targets)`
Add one or more ERC20 transfer calls.
```typescript theme={null}
builder.transfer(USDC_ADDRESS, [
{ to: "0xalice...", amount: 500_000n },
{ to: "0xbob...", amount: 300_000n },
]);
```
#### Parameters
* `token` (string): Token contract address
* `targets` (TransferTarget\[]): Array of `{ to: string, amount: bigint }` pairs
#### Returns
`this` (chainable)
### `calls()`
Returns a copy of the current call batch (for inspection).
```typescript theme={null}
const currentCalls = builder.calls();
console.log(`${currentCalls.length} calls queued`);
```
#### Returns
`Call[]`
### `send()`
Execute all batched calls atomically.
```typescript theme={null}
const result = await builder.send();
console.log(result.transaction_hash);
```
#### Returns
`Promise`
#### Throws
* If no calls have been added
* If `send()` was already called (builders are single-use)
### `sendSponsored()`
Execute via Chipi's paymaster (gasless). Requires a `PaymasterAdapter` in the constructor.
```typescript theme={null}
const txHash = await builder.sendSponsored();
console.log(txHash); // "0x..."
```
#### Returns
`Promise` (transaction hash)
#### Throws
* If no `paymaster` was configured in the constructor
* If no calls have been added
* If `send()` or `sendSponsored()` was already called (builders are single-use)
### `estimateFee()`
Estimate the fee for the current batch without executing.
```typescript theme={null}
const fee = await builder.estimateFee();
console.log(`Estimated fee: ${fee.overall_fee} wei`);
```
#### Returns
`Promise`
#### Throws
* If no calls have been added
### `preflight()`
Simulate the transaction without submitting. Returns the transaction trace and fee estimation. Useful for checking if a transaction would succeed before sending.
```typescript theme={null}
const sim = await builder.preflight();
console.log(`Would cost: ${sim.fee_estimation.overall_fee} wei`);
```
#### Returns
`Promise` with `transaction_trace` and `fee_estimation`
#### Throws
* If no calls have been added
## Examples
### Simple Transfer
```typescript theme={null}
const result = await new TxBuilder(account)
.transfer(USDC_ADDRESS, [{ to: recipient, amount: 1_500_000n }])
.send();
```
### Approve + Swap (Atomic Batch)
```typescript theme={null}
const result = await new TxBuilder(account)
.approve(USDC_ADDRESS, DEX_ADDRESS, 1_000_000n)
.add({
contractAddress: DEX_ADDRESS,
entrypoint: "swap",
calldata: [USDC_ADDRESS, ETH_ADDRESS, "1000000", "0"],
})
.send();
```
### Fee Preview Before Sending
```typescript theme={null}
const builder = new TxBuilder(account)
.transfer(USDC_ADDRESS, [{ to, amount }]);
const fee = await builder.estimateFee();
console.log(`This will cost: ${fee.overall_fee} wei`);
// User confirms, create new builder to send
const result = await new TxBuilder(account)
.transfer(USDC_ADDRESS, [{ to, amount }])
.send();
```
### With Erc20 and Amount
```typescript theme={null}
import { TxBuilder, Erc20, Amount, TokenRegistry } from "@chipi-stack/core";
const registry = new TokenRegistry("mainnet");
const usdcInfo = registry.bySymbol("USDC")!;
const usdc = new Erc20(usdcInfo, account);
const amount = Amount.parse("50.0", usdcInfo);
const result = await new TxBuilder(account)
.add(usdc.approveCall(spender, amount))
.add(usdc.transferCall(recipient, amount))
.send();
```
### Gasless with Argent X / Braavos (browser)
```typescript theme={null}
import { connect } from "starknetkit";
import { TxBuilder } from "@chipi-stack/core";
import { ChipiBrowserSDK } from "@chipi-stack/backend";
const { wallet } = await connect();
const sdk = new ChipiBrowserSDK({ apiPublicKey: "pk_..." });
const paymaster = sdk.createPaymasterAdapter(userBearerToken);
// wallet.account already has the Argent/Braavos signer — triggers popup on sign
const txHash = await new TxBuilder(wallet.account, { paymaster })
.transfer(USDC, [{ to: recipient, amount: 1_000_000n }])
.sendSponsored(); // popup -> sign -> gasless execution
```
### Gasless with Cartridge Controller
```typescript theme={null}
import Controller from "@cartridge/controller";
import { TxBuilder } from "@chipi-stack/core";
import { ChipiBrowserSDK } from "@chipi-stack/backend";
const controller = new Controller();
await controller.connect();
const sdk = new ChipiBrowserSDK({ apiPublicKey: "pk_..." });
const paymaster = sdk.createPaymasterAdapter(userBearerToken);
// controller IS Account-compatible
const txHash = await new TxBuilder(controller, { paymaster })
.transfer(USDC, [{ to, amount }])
.sendSponsored();
```
### Backend Automation (gasless)
```typescript theme={null}
import { createAccount, DirectSigner, TxBuilder } from "@chipi-stack/core";
import { ChipiServerSDK } from "@chipi-stack/backend";
const sdk = new ChipiServerSDK({ apiPublicKey: "pk_...", apiSecretKey: "sk_..." });
const paymaster = sdk.createPaymasterAdapter();
const provider = { nodeUrl: "https://starknet-mainnet.infura.io/v3/YOUR_KEY" };
const accountAddress = "0xYOUR_ACCOUNT_ADDRESS";
const signer = new DirectSigner(process.env.AUTOMATION_KEY!, accountAddress);
const account = await createAccount({ signer, provider });
const txHash = await new TxBuilder(account, { paymaster })
.approve(USDC, spender, amount)
.transfer(USDC, [{ to, amount }])
.sendSponsored(); // gasless, no popup
```
## Related
* [Amount](/sdk/core/amount): Type-safe token amounts for use with TxBuilder
* [Erc20](/sdk/core/erc20): Generate typed Call objects for approve/transfer
* [SignerAdapter](/sdk/core/signer-adapter): Different signing strategies for the Account
# Expo with Clerk
Source: https://docs.chipipay.com/sdk/expo/gasless-clerk-setup
Learn how to integrate Chipi Pay with your Expo application using Clerk Auth for authentication and biometric security.
# Building with Expo
This guide will walk you through integrating Chipi Pay into your Expo application with biometric authentication and secure storage. We'll cover everything from installation to implementing secure payment flows.
## Prerequisites
* Node.js 20.19.0 or later
* **Expo SDK 55 or later** (`@chipi-stack/chipi-expo` peer-requires `expo-local-authentication >=55.0.0` and `expo-secure-store >=55.0.0`)
* Expo CLI
* Basic knowledge of React Native
* A Chipi Pay account
* Clerk account for authentication
* Device with biometric authentication support (for biometric features)
## Getting Started
To get started with Chipi Pay in your Expo application, you'll need to have a basic Expo project set up. If you don't have one yet, you can create it using:
```bash theme={null}
npx create-expo-app@latest my-chipi-app
cd my-chipi-app
```
`create-expo-app@latest` currently scaffolds Expo SDK 54, but
`@chipi-stack/chipi-expo` requires SDK 55+ (for `expo-local-authentication`
and `expo-secure-store` peer dependencies). Upgrade before installing the
Chipi SDK:
```bash theme={null}
npx expo install expo@~55.0.12 expo-local-authentication expo-secure-store
```
Verified: `npx expo install` picks SDK-55-compatible versions automatically
(`expo-local-authentication@~55.0.12`, `expo-secure-store@~55.0.12`).
## Installation
After upgrading to SDK 55, install the remaining packages:
```bash theme={null}
# Install Chipi Pay SDK
npx expo install @chipi-stack/chipi-expo
# Install Clerk and authentication packages
# (expo-local-authentication and expo-secure-store already installed in the SDK 55 upgrade above)
npx expo install @clerk/clerk-expo expo-web-browser
```
## Configuration
1. Create a `.env` file in your project root and add your **publishable** API keys:
```bash theme={null}
EXPO_PUBLIC_CHIPI_API_PUBLIC_KEY=pk_dev_your_public_key_here
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_your_clerk_publishable_key
```
**Never put secret keys in an Expo client app.** `@chipi-stack/chipi-expo` is a
mobile SDK — treat `apiSecretKey` as backend-only and do not pass it from
client code. (Note: `ChipiProvider` accepts `ChipiSDKConfig` which includes
`apiSecretKey?: string` as optional — TypeScript will not block you, but
passing it bundles the secret into your app binary. The type-level guard
`apiSecretKey?: never` exists only on `ChipiBrowserSDKConfig` used by the
web SDK.) Clerk's `@clerk/clerk-expo` only accepts a `publishableKey` —
there is no `secretKey` prop on `ClerkProvider`. Secret keys
(`CHIPI_SECRET_KEY`, `CLERK_SECRET_KEY`) belong on your backend, never in a
mobile app bundle.
You can get your Chipi API public key [here](https://dashboard.chipipay.com/configure/credentials). And your Clerk publishable key [here](https://dashboard.clerk.com/last-active?path=api-keys).
2. Update your `app.json` to include the required permissions:
```json theme={null}
{
"expo": {
"plugins": [
[
"expo-local-authentication",
{
"faceIDPermission": "Allow $(PRODUCT_NAME) to use Face ID to unlock your wallet."
}
],
"expo-secure-store"
],
"ios": {
"supportsTablet": true,
"infoPlist": {
"NSFaceIDUsageDescription": "Allow $(PRODUCT_NAME) to use Face ID to unlock your wallet."
}
}
}
}
```
## Implementing Secure Authentication Flow
1. Wrap your app in `ClerkProvider` and `ChipiProvider`. In Expo Router projects (the default from `create-expo-app`), the root component lives at `app/_layout.tsx`:
```typescript theme={null}
// app/_layout.tsx
import { ClerkProvider } from "@clerk/clerk-expo";
import { ChipiProvider } from "@chipi-stack/chipi-expo";
import { tokenCache } from "@clerk/clerk-expo/token-cache";
import { Slot } from "expo-router";
export default function RootLayout() {
return (
);
}
```
`ChipiProvider` takes configuration via the `config` prop (`ChipiSDKConfig`).
In Expo usage, pass only public client-safe values (`apiPublicKey`, and
optionally `alphaUrl`/`nodeUrl`). Do not pass `apiPublicKey` as a direct
prop on `ChipiProvider`.
## Adding Clerk JWKS Endpoint
Go into your Clerk dashboard and copy the [JWKS endpoint](https://dashboard.clerk.com/last-active?path=api-keys) available in the developers tab.
Register JWKS Endpoint URL in the [dashboard](https://dashboard.chipipay.com/configure/jwks-endpoint).
## Next Steps
* Add error handling and loading states
* Customize the UI to match your app's design
Need help? Check out our [Telegram Community](https://t.me/+e2qjHEOwImkyZDVh) for
support and to connect with other developers.
# Expo with Custom Auth
Source: https://docs.chipipay.com/sdk/expo/gasless-custom-auth-setup
Integrate Chipi Pay with any OIDC-compliant auth provider (Auth0, Cognito, Okta, Keycloak) in your Expo application.
## What is JWKS?
JSON Web Key Set (JWKS) is a standard that allows Chipi Pay to verify your users' JWT tokens without ever seeing their credentials. Your auth provider exposes a public endpoint with its signing keys, and Chipi uses those keys to verify that tokens are authentic. Any OIDC-compliant provider (Auth0, Cognito, Okta, Keycloak, etc.) exposes a JWKS endpoint.
```bash theme={null}
npx expo install @chipi-stack/chipi-expo
```
Add your Chipi API key to your `.env` file:
```bash theme={null}
EXPO_PUBLIC_CHIPI_API_PUBLIC_KEY=your_chipi_api_public_key
```
You can get your API keys from the [Chipi Dashboard](https://dashboard.chipipay.com/configure/credentials).
Wrap your app with `ChipiProvider` alongside your auth provider:
```tsx theme={null}
// App.tsx
import { ChipiProvider } from "@chipi-stack/chipi-expo";
export default function App() {
return (
);
}
```
Use your auth provider's SDK to get a JWT token, then pass it to Chipi hooks:
```typescript theme={null}
// components/CreateWallet.tsx
import { useState } from "react";
import { View, TextInput, TouchableOpacity, Text } from "react-native";
import { useCreateWallet } from "@chipi-stack/chipi-expo";
// Replace with your auth provider's token method
async function getBearerToken(): Promise {
// Auth0: const { getAccessTokenSilently } = useAuth0();
// Cognito: const session = await Auth.currentSession();
// Okta: const { authState } = useOktaAuth();
return "your-jwt-token";
}
export default function CreateWallet() {
const { createWalletAsync, isLoading } = useCreateWallet();
const [encryptKey, setEncryptKey] = useState("");
const handleCreateWallet = async () => {
const bearerToken = await getBearerToken();
const response = await createWalletAsync({
params: { encryptKey },
bearerToken,
});
console.log("Wallet created:", response.publicKey);
};
return (
{isLoading ? "Creating..." : "Create Wallet"}
);
}
```
Register your auth provider's JWKS endpoint in the [Chipi Dashboard](https://dashboard.chipipay.com/configure/jwks-endpoint):
1. Go to Configure > Auth Provider
2. Select **Other** as your provider
3. Paste your provider's URL or JWKS endpoint
4. The dashboard will try OIDC discovery automatically
5. Click **Verify & Save**
### Compatible providers
| Provider | JWKS URL pattern |
| ----------- | ------------------------------------------------------------------------ |
| Auth0 | `https://YOUR_DOMAIN/.well-known/jwks.json` |
| AWS Cognito | `https://cognito-idp.REGION.amazonaws.com/POOL_ID/.well-known/jwks.json` |
| Okta | `https://YOUR_DOMAIN/oauth2/default/v1/keys` |
| Keycloak | `https://YOUR_HOST/realms/REALM/protocol/openid-connect/certs` |
Need help? Check out our [Telegram Community](https://t.me/+e2qjHEOwImkyZDVh) for
support and to connect with other developers.
# Expo with Firebase
Source: https://docs.chipipay.com/sdk/expo/gasless-firebase-setup
Learn how to integrate Chipi Pay with your Expo application using Firebase Auth for authentication and secure wallet storage.
1. First, in order to create authentication with Firebase you will need two things: first, a [Firebase account](https://firebase.google.com/) and second, a [Expo](https://expo.dev/) project.
Once you have your account you must:
* Go to your [console](https://console.firebase.google.com) and create a new Firebase project.
* Once you have a project, proceed to the `Authentication` section.
* Click on the `Sign in method` tab and enable the `Email/password` sign-in method and click `save`.
* Go to `project settings`, scroll and under your apps section click on the `web app` usually displayed with a `>` icon.
* Register your app and click save.
2. Install the npm package in your project:
```bash theme={null}
npm install firebase
```
On the Firebase Register web app page, you will also be provided something like this. Save it for later.
```typescript theme={null}
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: "apikey",
authDomain: "domain",
projectId: "your-id",
storageBucket: "storage-bucket",
messagingSenderId: "000000",
appId: "app-id"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
```
1. Create a file named `firebase.ts` with the following configuration:
```typescript theme={null}
import { initializeApp } from "firebase/app";
import { initializeAuth, getReactNativePersistence } from 'firebase/auth';
import ReactNativeAsyncStorage from '@react-native-async-storage/async-storage';
import { getFirestore } from "firebase/firestore";
import { getStorage } from 'firebase/storage';
const firebaseConfig = {
apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.EXPO_PUBLIC_FIREBASE_APP_ID,
};
// Initialize Firebase
export const firebaseApp = initializeApp(firebaseConfig)
export const auth = initializeAuth(firebaseApp, {
persistence: getReactNativePersistence(ReactNativeAsyncStorage)
});
export const db = getFirestore(firebaseApp);
export const storage = getStorage(firebaseApp);
```
2. Initialize metro config:
run the following command to initialize metro config
```bash theme={null}
npx expo customize metro.config.js
```
3. Edit your `metro.config.js` file
```typescript theme={null}
const { getDefaultConfig } = require('@expo/metro-config');
const defaultConfig = getDefaultConfig(__dirname);
defaultConfig.resolver.sourceExts.push('cjs');
module.exports = defaultConfig;
```
4. Go to `tsconfig.json` and add to paths for typescript issues
```json theme={null}
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"paths": {
"@firebase/auth": ["./node_modules/@firebase/auth/dist/index.rn.d.ts"],
"@/*": [
"./*"
]
}
},
"include": [
"**/*.ts",
"**/*.tsx",
".expo/types/**/*.ts",
"expo-env.d.ts"
]
}
```
5. If not already installed, install:
```bash theme={null}
npm install @react-native-async-storage/async-storage
```
Install the required packages:
```bash theme={null}
# Install Chipi Pay SDK
npm install @chipi-stack/chipi-expo
```
1. In your `.env` file, add your Chipi **publishable** API key:
```bash theme={null}
EXPO_PUBLIC_CHIPI_API_PUBLIC_KEY=your_chipi_api_public_key
```
Never put `CHIPI_SECRET_KEY` in an Expo app — the SDK explicitly forbids it
at the type level via `ChipiBrowserSDKConfig`. Secret keys belong on your
backend only.
2. Set up the Chipi Pay provider in your application:
```tsx theme={null}
// app/_layout.tsx
import { ChipiProvider } from "@chipi-stack/chipi-expo";
const API_PUBLIC_KEY = process.env.EXPO_PUBLIC_CHIPI_API_PUBLIC_KEY;
if (!API_PUBLIC_KEY) throw new Error("EXPO_PUBLIC_CHIPI_API_PUBLIC_KEY is not set");
export default function RootLayout() {
// ...
return (
);
}
```
Visit the [dashboard JWKS and JWT configuration](https://dashboard.chipipay.com/configure/jwks-endpoint), select `Firebase` as your auth provider, and paste this under the JWKS Endpoint URL.
```bash theme={null}
https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys
```
Visit the [Firebase Authentication guide](https://firebase.google.com/docs/auth/web/password-auth) to learn how to use `createUserWithEmailAndPassword` and create your sign up and login!
Below you will see a simple example of how you can get and pass your tokens to the different Chipi hooks:
```typescript theme={null}
import { useGetWallet } from "@chipi-stack/chipi-expo";
import { useEffect, useState } from "react";
import { View, Text, ActivityIndicator } from "react-native";
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "@/utils/firebase";
export default function Home() {
const [userId, setUserId] = useState(null);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
setUserId(user.uid);
}
});
return () => unsubscribe();
}, []);
const getToken = async (): Promise => {
const user = auth.currentUser;
if (!user) {
throw new Error("User not authenticated");
}
const token = await user.getIdToken();
return token;
};
const { data: wallet, isLoading } = useGetWallet({
getBearerToken: getToken,
params: {
externalUserId: userId || "",
},
});
return (
Home page
{isLoading && }
{wallet && {JSON.stringify(wallet, null, 2)}}
);
}
```
Note: If you have not yet created a wallet, you may see an error with a message like:
message:"Wallet not found for externalUserId: user\_id, apiPublicKey: pk\_prod\_something"
This error is expected and shows that your app is correctly contacting Chipi, but there is not yet a wallet for this user. Once you create a wallet, this error will disappear.
That's it! You should now have an initial working version of Chipi Pay integrated into your application. You can now start implementing various features like:
* Wallet Creations
* Sending tokens
* Signing transactions
* and more!
Need help? Check out our [Telegram Community](https://t.me/+e2qjHEOwImkyZDVh)
for support and to connect with other developers.
# Quickstart
Source: https://docs.chipipay.com/sdk/expo/gasless-quickstart
Quick setup guide for gasless transactions with Chipi SDK in Expo applications
Gasless transactions are only available in Starknet Mainnet.
You'll need to [add the JWKS Endpoint](https://dashboard.chipipay.com/configure/jwks-endpoint) from your auth provider to authenticate gasless transactions.
With this JWKS Endpoint setup, you can now run secure code in your frontend
using a rotating JWT token.
### Getting your JWKS Endpoint
For these common auth providers, you can get your JWKS Endpoint like this:
1. Go to your [API Keys](https://dashboard.clerk.com/last-active?path=api-keys)
2. Copy your **JWKS Endpoint** and paste it into the JWKS Endpoint field in your Chipi Dashboard.
3. Follow our documentation to add [Clerk and Chipi](/sdk/expo/gasless-clerk-setup) to your application.
See our tutorial on how to set up Chipi with [Firebase](/sdk/expo/gasless-firebase-setup)!.
Now go to your [Credentials page](https://dashboard.chipipay.com/configure/credentials) and click on your project.
Which will give you access to your **Public Key** (`pk_prod_xxxx`). Keep this handy as you'll need it to initialize the SDK.
You won't need the Secret Key (`sk_prod_xxxx`) for now.
```bash npm theme={null}
npm install @chipi-stack/chipi-expo@latest
```
```bash yarn theme={null}
yarn add @chipi-stack/chipi-expo@latest
```
```bash pnpm theme={null}
pnpm add @chipi-stack/chipi-expo@latest
```
1. Add your **Public Key** to your environment variables.
```bash theme={null}
EXPO_PUBLIC_CHIPI_API_PUBLIC_KEY=pk_prod_xxxx
```
Never put `CHIPI_SECRET_KEY` in an Expo app — the SDK explicitly forbids it
at the type level via `ChipiBrowserSDKConfig`. Secret keys belong on your
backend only.
2. Wrap your main `_layout.tsx` with the `ChipiProvider` component.
```tsx theme={null}
// app/_layout.tsx
import { ChipiProvider } from "@chipi-stack/chipi-expo";
const API_PUBLIC_KEY = process.env.EXPO_PUBLIC_CHIPI_API_PUBLIC_KEY;
if (!API_PUBLIC_KEY) throw new Error("API_PUBLIC_KEY is not set");
export default function RootLayout() {
// ...
return (
);
}
```
You're ready to use the SDK! We recommend starting with our **convenience hooks** that simplify common operations:
### useChipiWallet - Unified Wallet Management
```tsx theme={null}
import { useChipiWallet } from "@chipi-stack/chipi-expo";
import { View, Text, TouchableOpacity } from "react-native";
function WalletComponent() {
const { userId, getToken } = useYourAuthProvider();
const {
wallet,
hasWallet,
formattedBalance,
createWallet,
isLoadingWallet,
} = useChipiWallet({
externalUserId: userId,
getBearerToken: getToken,
});
if (!hasWallet) {
return (
createWallet({ encryptKey: "1234" })}>
Create Wallet
);
}
return Balance: ${formattedBalance} USDC;
}
```
Learn about all the options and return values
### useChipiSession - Gasless Session Keys (CHIPI wallets only)
```tsx theme={null}
import { useChipiWallet, useChipiSession } from "@chipi-stack/chipi-expo";
function GaslessTransactions() {
const { wallet } = useChipiWallet({ ... });
const {
hasActiveSession,
createSession,
registerSession,
executeWithSession,
} = useChipiSession({
wallet,
encryptKey: pin,
getBearerToken: getToken,
});
// Setup session once
const handleSetup = async () => {
await createSession();
await registerSession();
};
// Execute transactions without owner signature
const handleTransfer = async () => {
await executeWithSession([{
contractAddress: USDC_CONTRACT,
entrypoint: "transfer",
calldata: [recipient, amount, "0x0"],
}]);
};
}
```
Learn about session key management
That's it! You should now have an initial working version of Chipi Pay integrated into your application. Explore more features:
* [All Available Hooks](/sdk/expo/hooks/use-create-wallet) - Complete hook reference
* [Session Keys](/sdk/expo/hooks/use-chipi-session) - Enable gasless UX
* [Buying Services](/sdk/expo/buying-services) - Accept crypto payments
* [Biometrics](/sdk/expo/use-biometrics) - Secure PIN storage with Face ID / Touch ID
Need help? Join our [Telegram Community](https://t.me/+e2qjHEOwImkyZDVh) to ask us anything on your mind.
# useAddSessionKeyToContract
Source: https://docs.chipipay.com/sdk/expo/hooks/use-add-session-key-to-contract
Register a session key on the CHIPI wallet smart contract. This one-time operation requires owner signature and enables the session key to execute transactions.
Session keys only work with **CHIPI wallets**. This hook requires the owner's `encryptKey` (PIN) to sign the registration transaction.
## Usage
```typescript theme={null}
const {
addSessionKeyToContract,
addSessionKeyToContractAsync,
data,
isLoading,
error,
isSuccess,
reset
} = useAddSessionKeyToContract();
```
### Parameters
| Parameter | Type | Required | Description |
| ---------------------- | ------------- | -------- | ------------------------------------------------------------------- |
| `params.encryptKey` | string | Yes | Owner's PIN to decrypt wallet private key for signing |
| `params.wallet` | WalletData | Yes | Wallet object with `publicKey`, `encryptedPrivateKey`, `walletType` |
| `params.sessionConfig` | SessionConfig | Yes | Session configuration (see below) |
| `bearerToken` | string | Yes | Authentication token from your auth provider |
### SessionConfig Structure
```typescript theme={null}
{
sessionPublicKey: string; // From useCreateSessionKey().data.publicKey
validUntil: number; // Unix timestamp (seconds)
maxCalls: number; // Maximum transactions allowed
allowedEntrypoints: string[]; // Whitelisted function selectors (empty = all allowed)
}
```
### Return Value
| Property | Type | Description |
| ------------------------------ | ------------- | --------------------------------------------------- |
| `addSessionKeyToContract` | function | Trigger registration (fire-and-forget) |
| `addSessionKeyToContractAsync` | function | Trigger registration (returns Promise with tx hash) |
| `data` | string | Transaction hash of registration |
| `isLoading` | boolean | Whether registration is in progress |
| `isError` | boolean | Whether an error occurred |
| `error` | Error \| null | Error details if any |
| `isSuccess` | boolean | Whether registration succeeded |
| `reset` | function | Reset mutation state |
## Example Implementation
```typescript theme={null}
import {
useAddSessionKeyToContract,
useCreateSessionKey,
useGetWallet
} from "@chipi-stack/chipi-react";
export function RegisterSession() {
const { createSessionKeyAsync } = useCreateSessionKey();
const { addSessionKeyToContractAsync, isLoading, error } = useAddSessionKeyToContract();
const { data: wallet } = useGetWallet({
params: { externalUserId: "user-123" },
getBearerToken
});
const [pin, setPin] = useState('');
const handleRegisterSession = async () => {
const bearerToken = await getBearerToken();
// Step 1: Create session key
const session = await createSessionKeyAsync({
encryptKey: pin,
durationSeconds: 3600,
});
// Step 2: Register on contract
const txHash = await addSessionKeyToContractAsync({
params: {
encryptKey: pin,
wallet: {
publicKey: wallet.publicKey,
encryptedPrivateKey: wallet.encryptedPrivateKey,
walletType: "CHIPI",
},
sessionConfig: {
sessionPublicKey: session.publicKey,
validUntil: session.validUntil,
maxCalls: 100, // Allow 100 transactions
allowedEntrypoints: [], // Empty = all functions allowed
},
},
bearerToken,
});
// Store session for later use
localStorage.setItem('chipiSession', JSON.stringify(session));
console.log('Session registered:', txHash);
};
return (