Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
59 changes: 59 additions & 0 deletions src/mina-signer/tests/mina-signer-test-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Test Signer CLI

Command-line helper for drafting, signing, and broadcasting Mina payments via
the public GraphQL API. It wraps the `mina-signer` library so you can submit
transactions without wiring up a full wallet or SDK.

## Getting Started

- **Prerequisites:** Node.js 18+ (for native `fetch`) and npm.
- **Install dependencies:** `npm install`
- **Quick run:**
`node mina-test-signer.js <private_key> <recipient_address> [graphql_url] [nonce]`

The optional `graphql_url` flag lets you override the default target defined in
`config.js`.

## Workflow

1. `mina-test-signer.js` parses CLI arguments and wires the supporting services.
2. `graphql-client.js` sends the signed payload to the Mina daemon and can check
whether the transaction reached the pool.
3. `utils.js` provides small helpers for GraphQL string construction and CLI
validation.
4. `config.js` centralises network defaults and usage messaging.

Check warning on line 24 in src/mina-signer/tests/mina-signer-test-app/README.md

View workflow job for this annotation

GitHub Actions / Lint-Format-and-Typo-Check

Unknown word (centralises)

- `mina-test-signer.js` – CLI entry point orchestrating validation, signing,
submission, and pool verification.
- `payment-service.js` – Thin wrapper around `mina-signer` with sensible
defaults for MINA amounts and fees.
- `graphql-client.js` – Minimal fetch-based GraphQL transport for sending
payments and querying pooled commands.
- `utils.js` – GraphQL stringification helpers plus basic CLI argument
validation/parsing.
- `config.js` – Configuration constants and usage text surfaced by the CLI.
- `key/` – Sample key material for experimentation; do not use in production
environments.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see payment-service.js or key/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This list also echoes the list above without a title or enhancing description, are we sure we need both lists?


Check the console output for a transaction id; you can re-run the pool check or
the `getPooledUserCommands` helper to confirm inclusion. Provide a `nonce`
argument when you need to synchronise with on-chain account state manually. The

Check warning on line 40 in src/mina-signer/tests/mina-signer-test-app/README.md

View workflow job for this annotation

GitHub Actions / Lint-Format-and-Typo-Check

Unknown word (synchronise)
CLI prints emoji-enhanced step logs and a summary table so you can spot
successes and failures at a glance. GraphQL errors (including malformed
responses) cause the CLI to exit with a non-zero status so they can be surfaced
in scripts and CI.

## Private key format

For clarity, private key is in output format of:

```
mina advanced dump-keypair --privkey-path ...
```

## Safety Notes

- Treat private keys in plain text with care. Prefer environment variables or a
secure secrets manager for real deployments.
- The example keys under `key/` are for local testing only; they are publicly
known and should never hold funds.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a private key in plaintext somewhere in this repo?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's just the one we used for dry run network when testing hf. Randomly generated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the key/ folder still exist?

24 changes: 24 additions & 0 deletions src/mina-signer/tests/mina-signer-test-app/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Centralized configuration for Mina payment signing.
* Keeps network defaults and unit conversion helpers in one place so
* the rest of the code can remain declarative.
*/
export const CONFIG = {
NETWORK: 'testnet',
DEFAULT_GRAPHQL_URL: 'http://localhost:3085/graphql',
MINA_UNITS: {
ONE_MINA: 1000000000,
DEFAULT_AMOUNT_MULTIPLIER: 150,
DEFAULT_FEE_MULTIPLIER: 1
}
};

/**
* Human-friendly CLI usage text that `mina-test-signer.js` displays when
* the caller provides incomplete arguments.
*/
export const USAGE_INFO = {
message: 'Usage: node mina-test-signer.js <private_key> <recipient_address> [graphql_url] [nonce]',
example: 'Example: node mina-test-signer.js EKErBK1KznrJJY3raJafSyxSayJ6viejaVrmjzXkSmoxXiJQsesU B62qp4wcxoJyFFyXZ2RVw8kGPpWn6ncK4RtsTz29jFf6fY2XYN42R1v http://172.17.0.3:3085/graphql 3',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i hope this private key is random x)

defaultUrl: `Default GraphQL URL: ${CONFIG.DEFAULT_GRAPHQL_URL}`
};
177 changes: 177 additions & 0 deletions src/mina-signer/tests/mina-signer-test-app/graphql-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { GraphQLUtils } from './utils.js';

/**
* Minimal GraphQL transport layer responsible for broadcasting signed
* payments to a Mina daemon and inspecting the transaction pool.
*/
export class GraphQLClient {
constructor(url) {
this.url = url;
}

/**
* Posts a signed payment mutation to the configured GraphQL endpoint.
* Surfaces detailed errors while preserving the structured response
* the caller uses to confirm transaction submission.
*/
async sendPayment(signedPayment) {
const query = GraphQLUtils.createPaymentMutation(signedPayment);

console.log('\n🚀 Sending payment via GraphQL');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i am personally not a big fan of heavy emoji use in our code bsae but i dont want to block this PR because of that

console.log(`🌐 Endpoint: ${this.url}`);
console.log('📝 Mutation payload:');
console.log(query);

try {
const response = await fetch(this.url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query }),
});

return await this.handleResponse(response);
} catch (error) {
throw new Error(`Request error: ${error.message}`);
}
}

/**
* Normalizes the GraphQL response shape by either returning JSON data
* or throwing a rich error that upstream callers can surface.
*/
async handleResponse(response) {
if (response.status === 200) {
const rawBody = await response.text();

let json;
try {
json = JSON.parse(rawBody);
} catch (parseError) {
throw new Error(
`Unexpected JSON payload: ${parseError.message}. Raw response: ${rawBody}`
);
}

if (json.errors?.length) {
const combinedErrors = json.errors
.map(error => error.message ?? JSON.stringify(error))
.join(' | ');
throw new Error(`GraphQL errors: ${combinedErrors}`);
}

console.log('📦 GraphQL response payload:');
console.dir(json, { depth: null });
return json;
} else {
const text = await response.text();
throw new Error(`GraphQL error (${response.status}): ${text}`);
}
}

/**
* Queries the daemon's pooled commands and returns true when the given
* transaction ID is currently staged for inclusion in a block.
*/
async checkTransactionInPool(transactionId) {
const query = `
query MyQuery {
pooledUserCommands {
id
}
}
`;

try {
const response = await fetch(this.url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
operationName: 'MyQuery',
query,
variables: {}
}),
});

const rawBody = await response.text();
if (response.status !== 200) {
throw new Error(`GraphQL error (${response.status}): ${rawBody}`);
}

let json;
try {
json = JSON.parse(rawBody);
} catch (parseError) {
throw new Error(
`Unexpected JSON payload when checking pool: ${parseError.message}. Raw response: ${rawBody}`
);
}

if (json.errors?.length) {
const combinedErrors = json.errors
.map(error => error.message ?? JSON.stringify(error))
.join(' | ');
throw new Error(`GraphQL errors while checking pool: ${combinedErrors}`);
}

const pooledCommands = json.data?.pooledUserCommands || [];
return pooledCommands.some(command => command.id === transactionId);
} catch (error) {
console.error('Error checking transaction in pool:', error.message);
throw error;
}
}

/**
* Convenience method that lists transaction IDs in the current pool.
* Useful for manual debugging or exploratory scripts.
*/
async getPooledUserCommands() {
const query = `
query MyQuery {
pooledUserCommands {
id
}
}
`;

try {
const response = await fetch(this.url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
operationName: 'MyQuery',
query,
variables: {}
}),
});

const rawBody = await response.text();
if (response.status !== 200) {
throw new Error(`GraphQL error (${response.status}): ${rawBody}`);
}

let json;
try {
json = JSON.parse(rawBody);
} catch (parseError) {
throw new Error(
`Unexpected JSON payload when fetching pooled commands: ${parseError.message}. Raw response: ${rawBody}`
);
}

if (json.errors?.length) {
const combinedErrors = json.errors
.map(error => error.message ?? JSON.stringify(error))
.join(' | ');
throw new Error(`GraphQL errors while fetching pooled commands: ${combinedErrors}`);
}

console.log('📦 Pooled commands response payload:');
console.dir(json, { depth: null });
return json.data?.pooledUserCommands || [];
} catch (error) {
console.error('Error fetching pooled commands:', error.message);
throw error;
}
}
}
Loading
Loading