Skip to content

Latest commit

 

History

History
282 lines (231 loc) · 8.26 KB

File metadata and controls

282 lines (231 loc) · 8.26 KB
title Quickstart
description Go from zero to a working on-chain subscription in 5 minutes. Install the Vowena SDK, create a project and plan, subscribe a wallet, charge, and cancel.

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](https://dashboard.vowena.xyz) can fund a fresh wallet via friendbot if you don't have one. ```bash npm npm install @vowena/sdk ```
  ```bash pnpm
  pnpm add @vowena/sdk
  ```

  ```bash yarn
  yarn add @vowena/sdk
  ```
</CodeGroup>

<Tip>
  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.
</Tip>
```typescript import { VowenaClient, NETWORKS } from "@vowena/sdk";
    const client = new VowenaClient({
      contractId: NETWORKS.testnet.contractId,
      rpcUrl: NETWORKS.testnet.rpcUrl,
      networkPassphrase: NETWORKS.testnet.networkPassphrase,
    });
    ```
  </Tab>
  <Tab title="Mainnet">
    ```typescript
    // Mainnet deployment coming after testnet pilots.
    import { VowenaClient, NETWORKS } from "@vowena/sdk";

    const client = new VowenaClient({
      contractId: NETWORKS.mainnet.contractId,
      rpcUrl: NETWORKS.mainnet.rpcUrl,
      networkPassphrase: NETWORKS.mainnet.networkPassphrase,
    });
    ```
  </Tab>
  <Tab title="Custom RPC">
    ```typescript
    import { VowenaClient } from "@vowena/sdk";

    const client = new VowenaClient({
      contractId: "CABC...XYZ",
      rpcUrl: "https://your-rpc.example.com",
      networkPassphrase: "Test SDF Network ; September 2015",
    });
    ```
  </Tab>
</Tabs>

<Note>
  `NETWORKS.testnet` ships pre-configured `contractId`, `rpcUrl`,
  `networkPassphrase`, and `usdcAddress`.
</Note>
Every plan lives under a Project. Create one first.
```typescript
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];
```
```typescript 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));
```

<Accordion title="What does toStroops do?">
  Converts a human-readable amount to the 7-decimal integer format used
  by Stellar tokens:

  ```typescript
  toStroops("9.99"); // → 99900000n
  toStroops("1.00"); // → 10000000n
  ```
</Accordion>
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.
```typescript
const subTx = await client.buildSubscribe(
  "GSUBSCRIBER...ADDR",
  planId,
  // Optional: tune the allowance explicitly
  { allowancePeriods: 24 },
);

const subResult = await client.submitTransaction(await sign(subTx));
```

<Tip>
  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.
</Tip>
Once a billing period has elapsed, anyone can fire the charge. The contract does all the validation on-chain.
```typescript
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`.

<Info>
  You don't have to run your own keeper. The [Vowena
  dashboard](/dashboard/keeper-setup) ships a managed keeper cron that
  fires every minute.
</Info>
Either party can cancel, any time.
```typescript
const cancelTx = await client.buildCancel("GSUBSCRIBER...ADDR", subId);
await client.submitTransaction(await sign(cancelTx));
```

<Note>
  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.
</Note>

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

Projects, plans, subscriptions, allowances, and the billing lifecycle in depth. Visual walkthrough of the full protocol flow — plan creation to cancellation. Every method, type, and configuration option. Set up an automated keeper to charge subscriptions on schedule.