Skip to main content
Get a complete subscription lifecycle running on Stellar testnet in under 5 minutes.
You’ll need Node.js 20+, a funded Stellar testnet account, and a USDC trustline. The dashboard checkout page can fund a fresh wallet via friendbot if you don’t have one.
1

Install the SDK

npm install @vowena/sdk
The SDK is a lightweight wrapper around the Vowena Soroban contract. It builds transactions locally — you sign and submit them with your own wallet or keypair.
2

Create a client

import { VowenaClient, NETWORKS } from "@vowena/sdk";

const client = new VowenaClient({
  contractId: NETWORKS.testnet.contractId,
  rpcUrl: NETWORKS.testnet.rpcUrl,
  networkPassphrase: NETWORKS.testnet.networkPassphrase,
});
NETWORKS.testnet ships pre-configured contractId, rpcUrl, networkPassphrase, and usdcAddress.
3

Create a Project (merchant)

Every plan lives under a Project. Create one first.
const projectTx = await client.buildCreateProject({
  merchant: "GMERCHANT...ADDR",
  name: "Acme SaaS",
  description: "Recurring billing for Acme's hosted product.",
});

const projectResult = await client.submitTransaction(await sign(projectTx));
// Fetch the newly created project_id from the merchant's project list
const projectIds = await client.getMerchantProjects(
  "GMERCHANT...ADDR",
  "GMERCHANT...ADDR",
);
const projectId = projectIds[projectIds.length - 1];
4

Create a Plan (merchant)

import { toStroops, NETWORKS } from "@vowena/sdk";

const planTx = await client.buildCreatePlan({
  merchant: "GMERCHANT...ADDR",
  token: NETWORKS.testnet.usdcAddress,
  amount: toStroops("9.99"),           // 9.99 USDC per period
  period: 2_592_000,                   // 30 days
  priceCeiling: toStroops("14.99"),    // max price within this plan
  trialPeriods: 1,                     // 1 free period
  maxPeriods: 0,                       // 0 = unlimited
  gracePeriod: 259_200,                // 3 days grace on failed charges
  name: "Pro",                         // display name
  projectId,                           // parent project from step 3
});

const planResult = await client.submitTransaction(await sign(planTx));
Converts a human-readable amount to the 7-decimal integer format used by Stellar tokens:
toStroops("9.99"); // → 99900000n
toStroops("1.00"); // → 10000000n
5

Subscribe (subscriber)

One signature creates the subscription AND sets the SAC allowance. If the plan has no trial, the first period is charged atomically during this call.
const subTx = await client.buildSubscribe(
  "GSUBSCRIBER...ADDR",
  planId,
  // Optional: tune the allowance explicitly
  { allowancePeriods: 24 },
);

const subResult = await client.submitTransaction(await sign(subTx));
The subscriber’s wallet displays exactly what’s being approved: the token (USDC), the spending ceiling (price_ceiling × allowance_periods), and the Vowena contract as spender. Full transparency.
6

Charge (keeper or anyone)

Once a billing period has elapsed, anyone can fire the charge. The contract does all the validation on-chain.
const chargeTx = await client.buildCharge("GKEEPER...ADDR", subId);
await client.submitTransaction(await sign(chargeTx));
The contract checks:
  • Is the subscription active?
  • Has period seconds passed since the last charge?
  • Is the trial over?
  • Does the subscriber have sufficient balance and allowance?
  • Has max_periods been reached?
If all checks pass, USDC moves from subscriber to merchant via the SAC’s transfer_from.
You don’t have to run your own keeper. The Vowena dashboard ships a managed keeper cron that fires every minute.
7

Cancel

Either party can cancel, any time.
const cancelTx = await client.buildCancel("GSUBSCRIBER...ADDR", subId);
await client.submitTransaction(await sign(cancelTx));
Cancellation is immediate and on-chain. Subscribers can also cancel directly via any Soroban-aware wallet or block explorer — the Vowena frontend is not required.

Full example

import { VowenaClient, NETWORKS, toStroops } from "@vowena/sdk";

const client = new VowenaClient({
  contractId: NETWORKS.testnet.contractId,
  rpcUrl: NETWORKS.testnet.rpcUrl,
  networkPassphrase: NETWORKS.testnet.networkPassphrase,
});

// 1. Create a project
const projectTx = await client.buildCreateProject({
  merchant: "GMERCHANT...ADDR",
  name: "Acme SaaS",
  description: "",
});
await client.submitTransaction(await sign(projectTx));
const [projectId] = (
  await client.getMerchantProjects("GMERCHANT...ADDR", "GMERCHANT...ADDR")
).slice(-1);

// 2. Create a plan
const planTx = await client.buildCreatePlan({
  merchant: "GMERCHANT...ADDR",
  token: NETWORKS.testnet.usdcAddress,
  amount: toStroops("9.99"),
  period: 2_592_000,
  priceCeiling: toStroops("14.99"),
  trialPeriods: 0,
  maxPeriods: 12,
  gracePeriod: 259_200,
  name: "Pro",
  projectId,
});
await client.submitTransaction(await sign(planTx));
const [planId] = (
  await client.getMerchantPlans("GMERCHANT...ADDR", "GMERCHANT...ADDR")
).slice(-1);

// 3. Subscriber subscribes (first period charges atomically since trial=0)
const subTx = await client.buildSubscribe("GSUB...ADDR", planId);
await client.submitTransaction(await sign(subTx));
const [subId] = (
  await client.getSubscriberSubscriptions("GSUB...ADDR", "GSUB...ADDR")
).slice(-1);

// 4. After the next period elapses, anyone can charge
const chargeTx = await client.buildCharge("GKEEPER...ADDR", subId);
await client.submitTransaction(await sign(chargeTx));

// 5. Cancel
const cancelTx = await client.buildCancel("GSUB...ADDR", subId);
await client.submitTransaction(await sign(cancelTx));

Next steps

Core concepts

Projects, plans, subscriptions, allowances, and the billing lifecycle in depth.

How it works

Visual walkthrough of the full protocol flow — plan creation to cancellation.

SDK reference

Every method, type, and configuration option.

Run a keeper

Set up an automated keeper to charge subscriptions on schedule.