Skip to content
Open
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
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ API Documentation available here:

## JS SDK to interact with Streamflow protocols

This repo consists of js-sdk to interact with several protocol exposed by streamflow:
- `packages/stream` - [Core Streamflow Protocol](packages/stream/README.md) that allows to create a vesting/lock Stream to a Recipient;
- `packages/distributor` - [Distributor Streamflow Protocol](packages/distributor/README.md) that allows to Airdrop tokens to large amount of Recipients (thousands or even millions);
This repo contains the JS SDK used to interact with several protocols exposed by Streamflow:
- `packages/stream` - [Core Streamflow Protocol](packages/stream/README.md) that allows you to create a vesting/lock stream for a recipient;
- `packages/distributor` - [Distributor Streamflow Protocol](packages/distributor/README.md) that allows you to airdrop tokens to a large number of recipients (thousands or even millions);
- `packages/common` - Common utilities and types used by Streamflow SDK;

## Installation
Expand Down Expand Up @@ -56,8 +56,7 @@ For polyfills take a look on these libraries:

## Contributing

To contribute to this repository, please follow these steps:
[CONTRIBUTING](./CONTRIBUTING.md)
To contribute to this repository, see [CONTRIBUTING](./CONTRIBUTING.md).

## Release Notes
Check out our [RELEASE NOTES](./RELEASE_NOTES.md) for the latest updates and migration guides.
Expand Down
45 changes: 45 additions & 0 deletions packages/common/__tests__/lib/assertions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { PublicKey } from "@solana/web3.js";
import { describe, expect, test } from "vitest";

import { assertHasPublicKey, invariant } from "../../lib/assertions.js";

describe("invariant", () => {
test("does nothing when condition is truthy", () => {
expect(() => invariant(true)).not.toThrow();
expect(() => invariant(1)).not.toThrow();
});

test("throws with default message when condition is falsy", () => {
expect(() => invariant(false)).toThrow("Assertion failed");
});

test("includes string message when provided", () => {
expect(() => invariant(false, "missing field")).toThrow("Assertion failed: missing field");
});

test("supports lazy message function", () => {
expect(() =>
invariant(false, () => {
return "lazy detail";
}),
).toThrow("Assertion failed: lazy detail");
});
});

describe("assertHasPublicKey", () => {
const key = new PublicKey("So11111111111111111111111111111111111111112");

test("narrows type when publicKey is present", () => {
const wallet = { publicKey: key };
assertHasPublicKey(wallet);
expect(wallet.publicKey.equals(key)).toBe(true);
});

test("throws when publicKey is missing", () => {
expect(() => assertHasPublicKey({})).toThrow(/publicKey is missing/);
});

test("throws when publicKey is null", () => {
expect(() => assertHasPublicKey({ publicKey: null })).toThrow(/publicKey is missing/);
});
});
127 changes: 127 additions & 0 deletions packages/common/__tests__/lib/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import BN from "bn.js";
import { afterEach, describe, expect, test, vi } from "vitest";

import {
divCeilN,
getBN,
getNumberFromBN,
handleContractError,
multiplyBigIntByNumber,
sleep,
} from "../../lib/utils.js";

describe("getBN", () => {
test("whole tokens with zero decimals", () => {
expect(getBN(10, 0).toString()).toBe("10");
});

test("fractional value respects decimals", () => {
const bn = getBN(1.5, 9);
expect(bn.toString()).toBe("1500000000");
});

test("zero amount", () => {
expect(getBN(0, 6).toString()).toBe("0");
});
});

describe("getNumberFromBN", () => {
test("round-trip with moderate value", () => {
const decimals = 9;
const n = 123.456789;
const bn = getBN(n, decimals);
expect(getNumberFromBN(bn, decimals)).toBeCloseTo(n, 6);
});

test("uses division path when BN exceeds max safe integer but quotient fits in float", () => {
const decimals = 9;
// smallest-units value is huge, but value / 10^decimals is within MAX_SAFE_INTEGER
const smallestUnits = new BN("9007199254740990000000000");
const result = getNumberFromBN(smallestUnits, decimals);
expect(result).toBe(9007199254740990);
});
});

describe("divCeilN", () => {
test("divides with ceiling when remainder exists", () => {
expect(divCeilN(7n, 3n)).toBe(3n);
});

test("exact division has no extra increment", () => {
expect(divCeilN(6n, 3n)).toBe(2n);
});

test("zero numerator", () => {
expect(divCeilN(0n, 5n)).toBe(0n);
});
});

describe("multiplyBigIntByNumber", () => {
test("multiplies by fractional number using fixed-point scale", () => {
expect(multiplyBigIntByNumber(100n, 1.5)).toBe(150n);
});

test("returns zero for non-finite multiplier", () => {
expect(multiplyBigIntByNumber(100n, Number.NaN)).toBe(0n);
expect(multiplyBigIntByNumber(100n, Infinity)).toBe(0n);
});

test("returns zero when multiplier is zero", () => {
expect(multiplyBigIntByNumber(100n, 0)).toBe(0n);
});

test("applies sign from negative bigint and positive number", () => {
expect(multiplyBigIntByNumber(-10n, 2)).toBe(-20n);
});

test("applies sign from positive bigint and negative number", () => {
expect(multiplyBigIntByNumber(10n, -2)).toBe(-20n);
});
});

describe("sleep", () => {
afterEach(() => {
vi.useRealTimers();
});

test("resolves after the given delay", async () => {
vi.useFakeTimers();
const p = sleep(1000);
await vi.advanceTimersByTimeAsync(1000);
await expect(p).resolves.toBeUndefined();
});
});

describe("handleContractError", () => {
test("returns result when inner function succeeds", async () => {
const result = await handleContractError(async () => 42);
expect(result).toBe(42);
});

test("wraps Error in ContractError", async () => {
await expect(
handleContractError(async () => {
throw new Error("chain failed");
}),
).rejects.toMatchObject({ contractErrorCode: null, name: "ContractError" });
});

test("passes extracted code from callback", async () => {
await expect(
handleContractError(
async () => {
throw new Error("simulated");
},
() => "E_CUSTOM",
),
).rejects.toMatchObject({ contractErrorCode: "E_CUSTOM", name: "ContractError" });
});

test("rethrows non-Error values unchanged", async () => {
await expect(
handleContractError(async () => {
throw "string-throw";
}),
).rejects.toBe("string-throw");
});
});
3 changes: 2 additions & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"pack": "pnpm build && pnpm pack",
"lint": "eslint --fix .",
"lint-config": "eslint --print-config",
"prepublishOnly": "pnpm run lint && pnpm run build"
"test": "vitest run --passWithNoTests",
"prepublishOnly": "pnpm run lint && pnpm run test && pnpm run build"
},
"gitHead": "a37306eba0e762af096db642fa22f07194014cfd",
"devDependencies": {
Expand Down