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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/target
**/node_modules/
4 changes: 4 additions & 0 deletions tests/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
STACKS_CORE_RPC_HOST=localhost
STACKS_CORE_RPC_PORT=20443

POX_REWARD_LENGTH=20
98 changes: 98 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Solo stackers

This demo uses two solo stackers that generate their deposit address and use that
as PoX rewards address when stacking.

The stackers will use the signer key from devenv `signer-3` (for no specific reason)
when stacking.

## Install test dependencies

The scripts in `tests/` require `pnpm`.

Install the required dependencies for the demo:

```bash
cd tests
pnpm install --frozen-lockfile
```

## Run devenv

Ensure devenv (from sBTC) is running, and once Nakamoto is reached and the sBTC signers
bootstrap create a donation for them.

```bash
# From sBTC repo
make devenv-up
cargo run -p signer --bin demo-cli donation --amount 10000
```

## Fund the stackers

Fund the stackers with enough STX to get a slot.

```bash
# From sBTC repo
cargo run -p signer --bin demo-cli fund-stx --recipient ST2FQWJMF9CGPW34ZWK8FEPNK072NEV1VKRNBBMJ9 --amount 4000000000
cargo run -p signer --bin demo-cli fund-stx --recipient ST2BMYXHQ63YP410C57808B1K9APN38KDQM9A0E6S --amount 1000000000
```

## Populate env variables

The sBTC signers xonly pubkey is not constant, so we need to get the one for the
current devenv run. It also impacts the reward addresses, so we need to compute
those as well.

For demo purposes, use `. tests/solo/populate_env.sh` (repeated in the snippets below)
to export in the current shell the required env variables.

The script will:
1. Run `RUST_LOG=info cargo run -- -c tests/solo/spox.toml get-signers-xonly-key`
to get the signers aggregate address from the smart contract
2. Run `cargo run -- -c tests/solo/spox.toml get-deposit-address -n regtest`
to compute the deposit addresses of Alice and Bob
3. Export `SPOX_*` env vars with the info from the above steps

## Start the staking loop

The stackers need to stack the STX and extend the commit every once in a while.
To do so, run the following:

```bash
. tests/solo/populate_env.sh
(cd tests && pnpm exec tsx solo/demo.ts loop $SPOX_DEMO_ALICE_SK:$SPOX_DEMO_ALICE_BTC_ADDRESS $SPOX_DEMO_BOB_SK:$SPOX_DEMO_BOB_BTC_ADDRESS)
Comment thread
djordon-stacks marked this conversation as resolved.
```

## Run sPoX

With the above the stackers should start getting PoX payments, but without informing
Emily about it the sBTC signers will not process them. So, we run `spox`:

```bash
. tests/solo/populate_env.sh
cargo run -- -c tests/solo/spox.toml
```

It will keep looking for the pox payments and notify Emily.

## Clear exported env vars

If you need to rerun the demo in the same shell, you can clear the env vars with:
```bash
unset SPOX_DEPOSIT__ALICE__SIGNERS_XONLY SPOX_DEPOSIT__BOB__SIGNERS_XONLY SPOX_DEMO_ALICE_BTC_ADDRESS SPOX_DEMO_BOB_BTC_ADDRESS SPOX_DEMO_ALICE_SK SPOX_DEMO_BOB_SK
```

## Demo accounts

### Alice
```text
‣ Private Key: 3b1faa3d852d63860867557a9cf0587a06e89b9d8a385bee7139c7e65be7382401
‣ STX Address: ST2FQWJMF9CGPW34ZWK8FEPNK072NEV1VKRNBBMJ9
```

### Bob
```text
‣ Private Key: a46142592eaa52d38d17e07ddcf756c68a3efdab6a400fee9bc079ae7bfb3bf601
‣ STX Address: ST2BMYXHQ63YP410C57808B1K9APN38KDQM9A0E6S
```
76 changes: 76 additions & 0 deletions tests/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { STACKS_DEVNET } from '@stacks/network';
import pino, { Logger } from 'pino';
import 'dotenv/config'
import crypto from 'crypto';
import { getAddressFromPrivateKey } from '@stacks/transactions';
import { getPublicKeyFromPrivate } from '@stacks/encryption';
import { StackingClient } from '@stacks/stacking';

export let logger: Logger;
if (process.env.STACKS_LOG_JSON === '1') {
logger = pino({
level: process.env.LOG_LEVEL || 'debug',
});
} else {
logger = pino({
level: process.env.LOG_LEVEL || 'debug',
transport: {
target: 'pino-pretty',
options: {
colorize: true
},
},
});
}

export const nodeUrl = `http://${process.env.STACKS_CORE_RPC_HOST}:${process.env.STACKS_CORE_RPC_PORT}`;
export const network = STACKS_DEVNET;

Comment thread
matteojug-stacks marked this conversation as resolved.
export const POX_REWARD_LENGTH = parseEnvInt('POX_REWARD_LENGTH', true);

export const MAX_U128 = 2n ** 128n - 1n;
export const maxAmount = MAX_U128;

// We use devenv signer-3 key when we need to stack
const signer3Key = "3ec0ca5770a356d6cd1a9bfcbf6cd151eb1bd85c388cc00648ec4ef5853fdb7401";
export const signerKey = signer3Key;
export const signerStackAddr = getAddressFromPrivateKey(signerKey, network)
export const signerPubKey = getPublicKeyFromPrivate(signerKey)
export const signerStackingClient = new StackingClient({
address: signerStackAddr,
network,
});

export async function waitForSetup() {
try {
await signerStackingClient.getPoxInfo();
} catch (error) {
// @ts-ignore
if (/(ECONNREFUSED|ENOTFOUND|SyntaxError)/.test(error.cause?.message)) {
console.log(`Stacks node not ready, waiting...`);
}
await new Promise(resolve => setTimeout(resolve, 3000));
return waitForSetup();
Comment thread
matteojug-stacks marked this conversation as resolved.
}
}

export function parseEnvInt<T extends boolean = false>(
envKey: string,
required?: T
): T extends true ? number : number | undefined {
let value = process.env[envKey];
if (typeof value === 'undefined') {
if (required) {
throw new Error(`Missing required env var: ${envKey}`);
}
return undefined as T extends true ? number : number | undefined;
}
return parseInt(value, 10);
Comment thread
matteojug-stacks marked this conversation as resolved.
}

export function burnBlockToRewardCycle(burnBlock: number) {
const cycleLength = BigInt(POX_REWARD_LENGTH);
return Number(BigInt(burnBlock) / cycleLength) + 1;
}

export const randInt = () => crypto.randomInt(0, 0xffffffffffff);
23 changes: 23 additions & 0 deletions tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "stacking",
"version": "1.0.0",
"packageManager": "pnpm@10.33.0",
"description": "",
"type": "module",
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"@stacks/encryption": "7.4.0",
"@stacks/network": "7.3.1",
"@stacks/stacking": "7.4.0",
"@stacks/transactions": "7.4.0",
"dotenv": "17.3.1",
"pino": "10.3.1",
"pino-pretty": "10.3.1"
},
"devDependencies": {
"@types/node": "25.5.0",
"tsx": "4.21.0"
}
}
Loading
Loading