Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 75 additions & 3 deletions packages/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ npm install susuchain-sdk viem @stacks/network @stacks/transactions @stacks/conn

- **Multi-Chain ABI & Addresses**: Instantly access Solidity ABIs and mainnet contract addresses for both Celo and Stacks.
- **Stacks Browser Helpers**: Pre-packaged wallet triggers to easily call `create-circle`, `contribute`, and `trigger-payout` using the Leather wallet.
- **Stacks Server-Side Builders**: Construct and sign Stacks transactions with a private key for backend or scripting environments.
- **Celo Transaction Param Builders**: Generate ready-to-use contract call parameter objects for viem wallet clients.

## Usage

This SDK natively supports both **ES Modules (ESM)** and **CommonJS (CJS)** module systems.

### 📦 Importing the SDK
### Importing the SDK

#### Using ES Modules (import)
```typescript
Expand All @@ -33,7 +35,7 @@ import { SUSUCHAIN_CELO_ADDRESS, STACKS_CONTRACT_NAME } from 'susuchain-sdk';
const { SUSUCHAIN_CELO_ADDRESS, STACKS_CONTRACT_NAME } = require('susuchain-sdk');
```

### 🟡 Celo Integration (Viem)
### Celo Integration (Viem)

You can easily interact with the SusuChain smart contract on Celo using `viem`:

Expand All @@ -59,7 +61,39 @@ async function getCircleDetails(circleId: number) {
}
```

### 🟠 Stacks Integration (Leather Wallet)
### Celo Server-Side Transactions

Build contract call parameters and pass them to a viem wallet client:

```typescript
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { celo } from 'viem/chains';
import { buildCeloCreateCircleParams, buildCeloContributeParams } from 'susuchain-sdk';

const account = privateKeyToAccount('0x...');
const wallet = createWalletClient({ account, chain: celo, transport: http() });

// Create a savings circle
const createParams = buildCeloCreateCircleParams({
name: 'My Circle',
contributionWei: 1000000000000000000n, // 1 CELO
roundDurationDays: 30,
gracePeriodDays: 3,
penaltyFee: 0n,
members: ['0xAbc...', '0xDef...'],
});
const hash = await wallet.writeContract(createParams);

// Contribute to a circle
const contributeParams = buildCeloContributeParams({
circleId: 0,
valueWei: 1000000000000000000n,
});
const txHash = await wallet.writeContract(contributeParams);
```

### Stacks Integration (Leather Wallet)

Trigger Stacks smart contract interactions directly inside any web app:

Expand All @@ -82,6 +116,27 @@ callContribute(0, (data) => {
});
```

### Stacks Server-Side Transactions

Build, sign, and broadcast Stacks transactions from Node.js:

```typescript
import { buildCreateCircleTx, buildContributeTx, broadcastTx } from 'susuchain-sdk';

// Build and sign a create-circle transaction
const tx = await buildCreateCircleTx({
senderKey: 'your-private-key-hex',
name: 'Backend Circle',
contributionMicroSTX: 5_000_000,
members: ['SP2T02...', 'SP3...'],
fee: 2000,
});

// Broadcast the signed transaction
const result = await broadcastTx({ transaction: tx });
console.log('Broadcast result:', result);
```

## Exported Constants

### Celo
Expand All @@ -93,6 +148,23 @@ callContribute(0, (data) => {
- `STACKS_CONTRACT_NAME`: The contract name (`"susuchain"`).
- `STACKS_NETWORK`: The Stacks network configuration object (Mainnet).

## Exported Functions

### Browser Helpers (Stacks)
- `callCreateCircle(name, contributionMicroSTX, members, onFinish)`: Open Leather wallet to create a circle.
- `callContribute(circleId, onFinish)`: Open Leather wallet to contribute.
- `callTriggerPayout(circleId, onFinish)`: Open Leather wallet to trigger payout.

### Server-Side Builders (Stacks)
- `buildCreateCircleTx(opts)`: Build and sign a `create-circle` transaction.
- `buildContributeTx(opts)`: Build and sign a `contribute` transaction.
- `buildTriggerPayoutTx(opts)`: Build and sign a `trigger-payout` transaction.
- `broadcastTx(opts)`: Broadcast a signed transaction to the Stacks network.

### Parameter Builders (Celo)
- `buildCeloCreateCircleParams(opts)`: Returns viem-compatible params for `createCircle`.
- `buildCeloContributeParams(opts)`: Returns viem-compatible params for `contribute`.

## Tracking

This release satisfies clean ES Modules and CommonJS packaging guidelines in alignment with issue #27.
Expand Down
5 changes: 5 additions & 0 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@
"@stacks/transactions": "^7.0.0",
"@stacks/connect": "^8.0.0"
},
"peerDependenciesMeta": {
"@stacks/connect": {
"optional": true
}
},
"devDependencies": {
"tsup": "^8.0.2",
"typescript": "^5.0.0"
Expand Down
114 changes: 114 additions & 0 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import {
principalCV,
AnchorMode,
PostConditionMode,
makeContractCall,
broadcastTransaction,
} from "@stacks/transactions";
import type { StacksTransactionWire, TxBroadcastResult } from "@stacks/transactions";

/**
* Open Leather wallet to call create-circle
Expand Down Expand Up @@ -95,3 +98,114 @@ export function callTriggerPayout(
onCancel: () => console.log("trigger-payout cancelled"),
});
}

export async function buildCreateCircleTx(opts: {
senderKey: string;
name: string;
contributionMicroSTX: number;
members: string[];
fee?: number;
nonce?: number;
}): Promise<StacksTransactionWire> {
const tx = await makeContractCall({
contractAddress: STACKS_CONTRACT_ADDRESS,
contractName: STACKS_CONTRACT_NAME,
functionName: "create-circle",
functionArgs: [
stringAsciiCV(opts.name.slice(0, 50)),
uintCV(opts.contributionMicroSTX),
listCV(opts.members.slice(0, 10).map((m) => principalCV(m))),
],
senderKey: opts.senderKey,
network: STACKS_NETWORK,
fee: opts.fee,
nonce: opts.nonce,
});
return tx;
}

export async function buildContributeTx(opts: {
senderKey: string;
circleId: number;
fee?: number;
nonce?: number;
}): Promise<StacksTransactionWire> {
const tx = await makeContractCall({
contractAddress: STACKS_CONTRACT_ADDRESS,
contractName: STACKS_CONTRACT_NAME,
functionName: "contribute",
functionArgs: [uintCV(opts.circleId)],
senderKey: opts.senderKey,
network: STACKS_NETWORK,
postConditionMode: PostConditionMode.Allow,
fee: opts.fee,
nonce: opts.nonce,
});
return tx;
}

export async function buildTriggerPayoutTx(opts: {
senderKey: string;
circleId: number;
fee?: number;
nonce?: number;
}): Promise<StacksTransactionWire> {
const tx = await makeContractCall({
contractAddress: STACKS_CONTRACT_ADDRESS,
contractName: STACKS_CONTRACT_NAME,
functionName: "trigger-payout",
functionArgs: [uintCV(opts.circleId)],
senderKey: opts.senderKey,
network: STACKS_NETWORK,
postConditionMode: PostConditionMode.Allow,
fee: opts.fee,
nonce: opts.nonce,
});
return tx;
}

export async function broadcastTx(opts: {
transaction: StacksTransactionWire;
}): Promise<TxBroadcastResult> {
const result = await broadcastTransaction({
transaction: opts.transaction,
network: STACKS_NETWORK,
});
return result;
}

export function buildCeloCreateCircleParams(opts: {
name: string;
contributionWei: bigint;
roundDurationDays: number;
gracePeriodDays: number;
penaltyFee: bigint;
members: string[];
}) {
return {
address: SUSUCHAIN_CELO_ADDRESS as `0x${string}`,
abi: SUSUCHAIN_CELO_ABI,
functionName: "createCircle" as const,
args: [
opts.name,
opts.contributionWei,
BigInt(opts.roundDurationDays),
BigInt(opts.gracePeriodDays),
opts.penaltyFee,
opts.members as `0x${string}`[],
],
};
}

export function buildCeloContributeParams(opts: {
circleId: number;
valueWei: bigint;
}) {
return {
address: SUSUCHAIN_CELO_ADDRESS as `0x${string}`,
abi: SUSUCHAIN_CELO_ABI,
functionName: "contribute" as const,
args: [BigInt(opts.circleId)],
value: opts.valueWei,
};
}
26 changes: 25 additions & 1 deletion packages/sdk/test-cjs.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
const { SUSUCHAIN_CELO_ADDRESS, STACKS_CONTRACT_NAME } = require("./dist/index.js");
const {
SUSUCHAIN_CELO_ADDRESS,
STACKS_CONTRACT_NAME,
buildCreateCircleTx,
buildContributeTx,
buildTriggerPayoutTx,
broadcastTx,
buildCeloCreateCircleParams,
buildCeloContributeParams,
} = require("./dist/index.js");

if (SUSUCHAIN_CELO_ADDRESS !== "0x20B421Db767D3496E4489Db5C3122C1fD4625525") {
throw new Error("Invalid Celo contract address exported in CommonJS module");
Expand All @@ -7,4 +16,19 @@ if (STACKS_CONTRACT_NAME !== "susuchain") {
throw new Error("Invalid Stacks contract name exported in CommonJS module");
}

const builders = {
buildCreateCircleTx,
buildContributeTx,
buildTriggerPayoutTx,
broadcastTx,
buildCeloCreateCircleParams,
buildCeloContributeParams,
};

for (const [name, fn] of Object.entries(builders)) {
if (typeof fn !== "function") {
throw new Error(`Expected ${name} to be a function, got ${typeof fn}`);
}
}

console.log("CommonJS exports validated successfully.");
26 changes: 25 additions & 1 deletion packages/sdk/test-esm.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { SUSUCHAIN_CELO_ADDRESS, STACKS_CONTRACT_NAME } from "./dist/index.mjs";
import {
SUSUCHAIN_CELO_ADDRESS,
STACKS_CONTRACT_NAME,
buildCreateCircleTx,
buildContributeTx,
buildTriggerPayoutTx,
broadcastTx,
buildCeloCreateCircleParams,
buildCeloContributeParams,
} from "./dist/index.mjs";

if (SUSUCHAIN_CELO_ADDRESS !== "0x20B421Db767D3496E4489Db5C3122C1fD4625525") {
throw new Error("Invalid Celo contract address exported in ES Module");
Expand All @@ -7,4 +16,19 @@ if (STACKS_CONTRACT_NAME !== "susuchain") {
throw new Error("Invalid Stacks contract name exported in ES Module");
}

const builders = {
buildCreateCircleTx,
buildContributeTx,
buildTriggerPayoutTx,
broadcastTx,
buildCeloCreateCircleParams,
buildCeloContributeParams,
};

for (const [name, fn] of Object.entries(builders)) {
if (typeof fn !== "function") {
throw new Error(`Expected ${name} to be a function, got ${typeof fn}`);
}
}

console.log("ES Module exports validated successfully.");
Loading