@wasit-dev/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +155 -0
- package/package.json +49 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { checkStatus, runMppChannelSuite, runMppChargeSuite, runX402PaymentChecks, runX402ReadChecks, summarize, } from "@wasit-dev/core";
|
|
5
|
+
/**
|
|
6
|
+
* Accumulates repeated --header flags into one object.
|
|
7
|
+
*
|
|
8
|
+
* Exits 2 rather than 1 on a malformed value: nothing was learned about the
|
|
9
|
+
* target, so this is a configuration error, not a conformance failure.
|
|
10
|
+
*/
|
|
11
|
+
function collectHeader(value, previous) {
|
|
12
|
+
const separator = value.indexOf(":");
|
|
13
|
+
if (separator < 1) {
|
|
14
|
+
console.error(`Invalid --header "${value}". Expected "Name: value".`);
|
|
15
|
+
process.exit(2);
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
...(previous ?? {}),
|
|
19
|
+
[value.slice(0, separator).trim()]: value.slice(separator + 1).trim(),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const program = new Command();
|
|
23
|
+
program
|
|
24
|
+
.name("wasit")
|
|
25
|
+
.description("Protocol-compliance testing for x402 / MPP on Stellar")
|
|
26
|
+
.version("0.1.0");
|
|
27
|
+
/**
|
|
28
|
+
* Prints results and returns the process exit code.
|
|
29
|
+
*
|
|
30
|
+
* A skipped check carries `pass: false` so it can never be counted as
|
|
31
|
+
* conformance, but it is not a failure and must not fail the run. An errored
|
|
32
|
+
* check produced no verdict at all and is reported separately again.
|
|
33
|
+
*/
|
|
34
|
+
function report(results) {
|
|
35
|
+
for (const result of results) {
|
|
36
|
+
const flag = result.destructive ? " [destructive]" : "";
|
|
37
|
+
console.log(`${checkStatus(result)} ${result.id} ${result.name}${flag}`);
|
|
38
|
+
console.log(` ${result.detail}\n`);
|
|
39
|
+
}
|
|
40
|
+
const counts = summarize(results);
|
|
41
|
+
const line = [`${counts.passed} passed`];
|
|
42
|
+
if (counts.failed > 0)
|
|
43
|
+
line.push(`${counts.failed} failed`);
|
|
44
|
+
if (counts.errored > 0)
|
|
45
|
+
line.push(`${counts.errored} could not run`);
|
|
46
|
+
if (counts.skipped > 0)
|
|
47
|
+
line.push(`${counts.skipped} skipped`);
|
|
48
|
+
console.log(`${line.join(", ")}.`);
|
|
49
|
+
if (counts.errored > 0 && counts.failed === 0) {
|
|
50
|
+
console.log("\nNo verdict: some checks never reached the target or the run is " +
|
|
51
|
+
"misconfigured. This is not a statement about the target's conformance.");
|
|
52
|
+
}
|
|
53
|
+
return counts.exitCode;
|
|
54
|
+
}
|
|
55
|
+
program
|
|
56
|
+
.command("test")
|
|
57
|
+
.description("Run x402 compliance checks against a target service")
|
|
58
|
+
.requiredOption("--target <url>", "URL of the service to test")
|
|
59
|
+
.option("--network <network>", "Network identifier", "stellar:testnet")
|
|
60
|
+
.option("--payer-key <key>", "Testnet payer secret key (overrides STELLAR_PRIVATE_KEY from .env)")
|
|
61
|
+
.option("--method <verb>", "HTTP method the paid endpoint uses (default: GET). Paid endpoints that " +
|
|
62
|
+
"compute something usually take POST.")
|
|
63
|
+
.option("--body <json>", "Request body, sent verbatim. Implies Content-Type: application/json.")
|
|
64
|
+
.option("--header <name:value>", "Extra request header the endpoint needs before it will issue a challenge. Repeatable.", collectHeader)
|
|
65
|
+
.option("--read-only", "Skip payment checks (X402-06/07)", false)
|
|
66
|
+
.action(async (opts) => {
|
|
67
|
+
const shape = {
|
|
68
|
+
...(opts.method ? { method: opts.method } : {}),
|
|
69
|
+
...(opts.body !== undefined ? { body: opts.body } : {}),
|
|
70
|
+
...(opts.header ? { headers: opts.header } : {}),
|
|
71
|
+
};
|
|
72
|
+
const results = await runX402ReadChecks({ target: opts.target, ...shape });
|
|
73
|
+
const payerKey = opts.payerKey ?? process.env.STELLAR_PRIVATE_KEY;
|
|
74
|
+
if (opts.readOnly) {
|
|
75
|
+
console.log("(--read-only set: skipping payment checks)\n");
|
|
76
|
+
}
|
|
77
|
+
else if (!payerKey) {
|
|
78
|
+
console.log("(no payer key: set STELLAR_PRIVATE_KEY in .env or pass --payer-key — skipping payment checks)\n");
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
console.log("X402-06 settles a real payment and X402-07 attempts one. Testnet funds will move.\n");
|
|
82
|
+
results.push(...(await runX402PaymentChecks({
|
|
83
|
+
target: opts.target,
|
|
84
|
+
network: opts.network,
|
|
85
|
+
payerSecretKey: payerKey,
|
|
86
|
+
...shape,
|
|
87
|
+
})));
|
|
88
|
+
}
|
|
89
|
+
process.exit(report(results));
|
|
90
|
+
});
|
|
91
|
+
program
|
|
92
|
+
.command("mpp-channel")
|
|
93
|
+
.description("Run MPP channel-mode compliance checks against a target service")
|
|
94
|
+
.requiredOption("--target <url>", "URL of the paid resource to test")
|
|
95
|
+
.option("--commitment-key <hex>", "Raw ed25519 commitment seed, hex (default: COMMITMENT_SECRET_HEX)")
|
|
96
|
+
.option("--network <network>", "CAIP-2 network id (default: MPP_STELLAR_NETWORK)")
|
|
97
|
+
.option("--rpc-url <url>", "Override the default Soroban RPC endpoint")
|
|
98
|
+
.option("--channel <address>", "Assert which channel the target bills through. Defaults to the channel " +
|
|
99
|
+
"the target advertises; a mismatch fails MPP-10. (env: CHANNEL_CONTRACT)")
|
|
100
|
+
.option("--expect-token <address>", "MPP-10: expected token contract")
|
|
101
|
+
.option("--expect-from <address>", "MPP-10: expected funder address")
|
|
102
|
+
.option("--expect-to <address>", "MPP-10: expected recipient address")
|
|
103
|
+
.option("--expect-refund-period <ledgers>", "MPP-10: expected refund waiting period")
|
|
104
|
+
.option("--allow-destructive", "Enable MPP-13. Closing settles on-chain and permanently ends the channel.", false)
|
|
105
|
+
.option("--destructive-channel <address>", "Channel MPP-13 is permitted to close (default: CHANNEL_CONTRACT_DISPOSABLE)")
|
|
106
|
+
.action(async (opts) => {
|
|
107
|
+
const commitmentSecretHex = opts.commitmentKey ?? process.env.COMMITMENT_SECRET_HEX;
|
|
108
|
+
if (!commitmentSecretHex) {
|
|
109
|
+
console.error("No commitment key. Pass --commitment-key or set COMMITMENT_SECRET_HEX in .env.");
|
|
110
|
+
process.exit(2);
|
|
111
|
+
}
|
|
112
|
+
const network = opts.network ?? process.env.MPP_STELLAR_NETWORK ?? "stellar:testnet";
|
|
113
|
+
const channelOverride = opts.channel ?? process.env.CHANNEL_CONTRACT;
|
|
114
|
+
const destructiveChannel = opts.destructiveChannel ?? process.env.CHANNEL_CONTRACT_DISPOSABLE;
|
|
115
|
+
const refundWaitingPeriod = Number(opts.expectRefundPeriod);
|
|
116
|
+
const results = await runMppChannelSuite({
|
|
117
|
+
target: opts.target,
|
|
118
|
+
commitmentSecretHex,
|
|
119
|
+
network,
|
|
120
|
+
allowDestructive: opts.allowDestructive === true,
|
|
121
|
+
...(opts.rpcUrl ? { rpcUrl: opts.rpcUrl } : {}),
|
|
122
|
+
...(channelOverride ? { channelOverride } : {}),
|
|
123
|
+
...(destructiveChannel ? { destructiveChannel } : {}),
|
|
124
|
+
expected: {
|
|
125
|
+
...(opts.expectToken ? { token: opts.expectToken } : {}),
|
|
126
|
+
...(opts.expectFrom ? { from: opts.expectFrom } : {}),
|
|
127
|
+
...(opts.expectTo ? { to: opts.expectTo } : {}),
|
|
128
|
+
...(Number.isInteger(refundWaitingPeriod) ? { refundWaitingPeriod } : {}),
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
process.exit(report(results));
|
|
132
|
+
});
|
|
133
|
+
program
|
|
134
|
+
.command("mpp-charge")
|
|
135
|
+
.description("Run the MPP charge-mode check (MPP-01) against a target service")
|
|
136
|
+
.requiredOption("--target <url>", "URL of the paid resource to test")
|
|
137
|
+
.option("--payer-key <key>", "Payer secret key, S... (default: MPP_PAYER_SECRET)")
|
|
138
|
+
.option("--network <network>", "CAIP-2 network id (default: MPP_STELLAR_NETWORK)")
|
|
139
|
+
.option("--rpc-url <url>", "Override the default Soroban RPC endpoint")
|
|
140
|
+
.action(async (opts) => {
|
|
141
|
+
const payerSecretKey = opts.payerKey ?? process.env.MPP_PAYER_SECRET;
|
|
142
|
+
if (!payerSecretKey) {
|
|
143
|
+
console.error("No payer key. Pass --payer-key or set MPP_PAYER_SECRET in .env.");
|
|
144
|
+
process.exit(2);
|
|
145
|
+
}
|
|
146
|
+
console.log("MPP-01 settles a real payment. If the target is reachable, testnet funds will move.\n");
|
|
147
|
+
const results = await runMppChargeSuite({
|
|
148
|
+
target: opts.target,
|
|
149
|
+
network: opts.network ?? process.env.MPP_STELLAR_NETWORK ?? "stellar:testnet",
|
|
150
|
+
payerSecretKey,
|
|
151
|
+
...(opts.rpcUrl ? { rpcUrl: opts.rpcUrl } : {}),
|
|
152
|
+
});
|
|
153
|
+
process.exit(report(results));
|
|
154
|
+
});
|
|
155
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wasit-dev/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for x402/MPP protocol-conformance testing on Stellar \u2014 verifies a service's payment flow settles on-chain per spec.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"wasit": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/dzakwannajmi/wasit.git",
|
|
15
|
+
"directory": "packages/cli"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/dzakwannajmi/wasit#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/dzakwannajmi/wasit/issues"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"stellar",
|
|
23
|
+
"x402",
|
|
24
|
+
"mpp",
|
|
25
|
+
"soroban",
|
|
26
|
+
"conformance-testing",
|
|
27
|
+
"cli",
|
|
28
|
+
"payments"
|
|
29
|
+
],
|
|
30
|
+
"author": "Muhammad Dzakwan Najmi",
|
|
31
|
+
"license": "Apache-2.0",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=24"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.json"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@stellar/mpp": "^0.7.1",
|
|
43
|
+
"@stellar/stellar-sdk": "^16.1.0",
|
|
44
|
+
"@wasit-dev/core": "^0.1.0",
|
|
45
|
+
"commander": "^12.0.0",
|
|
46
|
+
"dotenv": "^16.0.0",
|
|
47
|
+
"mppx": "^0.8.14"
|
|
48
|
+
}
|
|
49
|
+
}
|