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
11 changes: 11 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,14 @@ jobs:
run: |
test -f target/deploy/nirvana.so
test -f target/idl/nirvana_protocol.json

- name: Install Node dependencies
run: npm install

- name: Generate CI wallet
run: |
mkdir -p "$HOME/.config/solana"
solana-keygen new --no-bip39-passphrase -o "$HOME/.config/solana/id.json" --force

- name: Run integration tests
run: chmod +x run-tests.sh && ./run-tests.sh
45 changes: 23 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ Before setting up, ensure you have the following installed:
Clone the repository and install dependencies:

```bash
git clone https://github.com/soraonchain-byte/Nirvana.git
cd Nirvana
git clone https://github.com/rikokurnia/nirvana-infinity.git
cd nirvana-infinity
npm install
```

Expand All @@ -32,37 +32,38 @@ npm install
To compile the smart contract and generate the IDL:

```bash
anchor build
CARGO_TARGET_DIR=$PWD/target anchor build --ignore-keys
```

Note: This will generate the `target/` folder containing the program binary (`nirvana.so`) and IDL (`nirvana_protocol.json`).
Note: `--ignore-keys` is required when the local deploy keypair does not match the program ID declared in source (`FxPnV48...`). This generates `target/deploy/nirvana.so` and `target/idl/nirvana_protocol.json`.

### 4. Running Tests

**Option A: Manual (recommended)**
**Option A: Test script (recommended)**

```bash
# Terminal 1: Start local validator
solana-test-validator --reset

# Terminal 2: Deploy and test
solana config set --url localhost --keypair ~/.config/solana/id.json
solana airdrop 100
solana program deploy target/deploy/nirvana.so --program-id target/deploy/nirvana-keypair.json
ANCHOR_WALLET=~/.config/solana/id.json \
ANCHOR_PROVIDER_URL=http://localhost:8899 \
npx ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts
chmod +x run-tests.sh
./run-tests.sh
```

**Option B: Anchor test**
The script starts a local validator, loads the program at the declared devnet ID via `--bpf-program`, and runs the full Mocha suite (~2 minutes).

Remove the `[scripts]` section from `Anchor.toml` (if present), then:
**Option B: Manual**

```bash
anchor test
# Terminal 1
solana-test-validator --reset \
--bpf-program FxPnV48rg9KkK6huUimjcjL9H4xssM8n7j3uva8k9tmc \
target/deploy/nirvana.so

# Terminal 2
ANCHOR_WALLET=~/.config/solana/id.json \
ANCHOR_PROVIDER_URL=http://localhost:8899 \
NODE_OPTIONS="--no-experimental-strip-types" \
npm test
```

Note: The test suite validates stream creation, time validation, and token transfers.
Note: The test suite covers stream creation, cliff/milestone vesting, cancel splits, arbiter triggers, and `top_up`.

---

Expand Down Expand Up @@ -121,9 +122,9 @@ solana program deploy target/deploy/nirvana.so --program-id target/deploy/nirvan

## 🤖 CI/CD Integration
This repository is equipped with **GitHub Actions**. Every push or pull request triggers a workflow that:
1. Sets up the Solana/Anchor environment.
2. Runs `anchor build` to check for compilation errors.
3. Executes the test suite to ensure no regressions.
1. Sets up Solana 3.1 + Anchor 1.0.
2. Runs `anchor build --ignore-keys` and verifies build artifacts.
3. Executes `./run-tests.sh` (29 integration tests).

---

Expand Down
2 changes: 1 addition & 1 deletion frontend/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Nirvana Protocol — Frontend

Next.js 15 App Router frontend for the [Nirvana Protocol](https://github.com/soraonchain-byte/Nirvana) equity-streaming smart contract on Solana.
Next.js 15 App Router frontend for the [Nirvana Protocol](https://github.com/rikokurnia/nirvana-infinity) equity-streaming smart contract on Solana.

**Live:** https://nirvana-infinity.vercel.app
**Program ID (Devnet):** `FxPnV48rg9KkK6huUimjcjL9H4xssM8n7j3uva8k9tmc`
Expand Down
62 changes: 53 additions & 9 deletions tests/nirvana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const CANCELLED_ACCOUNT_GONE =
// ConstraintSeeds (before, or instead of, the has_one ConstraintHasOne).
const UNAUTHORIZED = /ConstraintSeeds|ConstraintHasOne|seeds constraint|has one constraint/i;

// Anchor may surface the error code name or the human-readable #[msg] string.
const STREAM_EXPIRED = /StreamExpired|Stream expired/i;

describe("Nirvana Protocol - Complete Test Suite", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
Expand All @@ -52,6 +55,13 @@ describe("Nirvana Protocol - Complete Test Suite", () => {
// Global payer for mint creation
const mintAuthority = anchor.web3.Keypair.generate();

// Unique nonce per stream (matches frontend/lib/anchor.ts PDA seeds).
let nextNonce = 1;

function nonceToBuffer(nonce: anchor.BN): Buffer {
return nonce.toArrayLike(Buffer, "le", 8);
}

async function airdrop(pubkey: anchor.web3.PublicKey, amount: number) {
const sig = await provider.connection.requestAirdrop(pubkey, amount);
const latestBlockHash = await provider.connection.getLatestBlockhash();
Expand Down Expand Up @@ -114,10 +124,19 @@ describe("Nirvana Protocol - Complete Test Suite", () => {
return { authority, recipient, authorityTokenAccount, recipientTokenAccount };
}

// Helper: derive PDAs
function getPDAs(authority: anchor.web3.PublicKey, recipient: anchor.web3.PublicKey) {
// Helper: derive PDAs (nonce-seeded — must match programs/nirvana/src/lib.rs)
function getPDAs(
authority: anchor.web3.PublicKey,
recipient: anchor.web3.PublicKey,
nonce: anchor.BN
) {
const [statePda] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("state"), authority.toBuffer(), recipient.toBuffer()],
[
Buffer.from("state"),
authority.toBuffer(),
recipient.toBuffer(),
nonceToBuffer(nonce),
],
program.programId
);
const [vaultPda] = anchor.web3.PublicKey.findProgramAddressSync(
Expand All @@ -140,9 +159,11 @@ describe("Nirvana Protocol - Complete Test Suite", () => {
endTime?: anchor.BN;
cliffTime?: anchor.BN;
arbiter?: anchor.web3.PublicKey | null;
nonce?: anchor.BN;
}
) {
const now = Math.floor(Date.now() / 1000);
const nonce = params.nonce ?? new anchor.BN(nextNonce++);
const baseAmount = params.baseAmount ?? new anchor.BN(100_000_000);
const cliffAmount = params.cliffAmount ?? new anchor.BN(0);
const milestoneAmount = params.milestoneAmount ?? new anchor.BN(50_000_000);
Expand All @@ -151,10 +172,23 @@ describe("Nirvana Protocol - Complete Test Suite", () => {
const cliffTime = params.cliffTime ?? new anchor.BN(now + 3);
const arbiter = params.arbiter ?? null;

const { statePda, vaultPda } = getPDAs(params.authority.publicKey, params.recipient);
const { statePda, vaultPda } = getPDAs(
params.authority.publicKey,
params.recipient,
nonce
);

await program.methods
.createStream(baseAmount, cliffAmount, milestoneAmount, startTime, endTime, cliffTime, arbiter)
.createStream(
nonce,
baseAmount,
cliffAmount,
milestoneAmount,
startTime,
endTime,
cliffTime,
arbiter
)
.accounts({
authority: params.authority.publicKey,
recipient: params.recipient,
Expand All @@ -168,7 +202,17 @@ describe("Nirvana Protocol - Complete Test Suite", () => {
.signers([params.authority])
.rpc();

return { statePda, vaultPda, baseAmount, cliffAmount, milestoneAmount, startTime, endTime, cliffTime };
return {
statePda,
vaultPda,
nonce,
baseAmount,
cliffAmount,
milestoneAmount,
startTime,
endTime,
cliffTime,
};
}

// =========================================================================
Expand Down Expand Up @@ -507,7 +551,7 @@ describe("Nirvana Protocol - Complete Test Suite", () => {
assert.fail("Should have thrown StreamExpired");
} catch (err: any) {
const t = errText(err);
assert.include(t, "StreamExpired", `expected StreamExpired, got: ${t}`);
assert.match(t, STREAM_EXPIRED, `expected stream expired error, got: ${t}`);
}
});

Expand Down Expand Up @@ -708,10 +752,10 @@ describe("Nirvana Protocol - Complete Test Suite", () => {
const recipientBalance = await getTokenBalance(recipientTokenAccount);
const creatorBalanceAfter = await getTokenBalance(authorityTokenAccount);

// Recipient gets linear (~50) + triggered milestone (50) ~= 100. Wide band
// Recipient gets linear (~50) + triggered milestone (50) ~= 100–120. Wide band
// absorbs validator clock drift on the linear part.
assert.isAbove(recipientBalance, 80);
assert.isBelow(recipientBalance, 120);
assert.isAtMost(recipientBalance, 120);

// Creator gets back 150 - recipient (the unvested linear remainder ~50).
const creatorGain = creatorBalanceAfter - creatorBalanceBefore;
Expand Down
Loading