Skip to content
Draft
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
42 changes: 33 additions & 9 deletions docs/node-operators/rosetta/samples/index.mdx
Original file line number Diff line number Diff line change
@@ -1,21 +1,45 @@
---
title: Code Samples
description: Curl-based examples for common Rosetta API operations
description: TypeScript and shell examples for common Mina Rosetta API operations
---

# Code Samples

These samples use `curl` and [`jq`](https://jqlang.github.io/jq/) to interact with the Rosetta API. They assume a running Rosetta instance on `localhost:3087`.
This section covers the Mina-specific bits of integrating with Rosetta. For each common task there is a short narrative page on what the pattern does and which Mina-specific objects are involved, plus a link to a runnable script in the mina repo that you can copy into your own integration.

Set these shell variables before running the examples:
## Runnable examples

Working TypeScript scripts live in the mina repo under [`src/app/rosetta/examples/ts`](https://github.com/MinaProtocol/mina/tree/develop/src/app/rosetta/examples/ts). They use [`@o1-labs/mina-rosetta-sdk`](https://www.npmjs.com/package/@o1-labs/mina-rosetta-sdk) for the typed Rosetta HTTP surface and [`mina-signer`](https://www.npmjs.com/package/mina-signer) for transaction signing. The SDK is a light wrapper — typed endpoints Mina actually exposes plus the Mina-specific operation-builder helpers (`buildTransferOperations`, `buildDelegationOperations`).

Quickstart:

```bash
ROSETTA_URL="http://localhost:3087"
NETWORK='{"blockchain":"mina","network":"mainnet"}'
git clone https://github.com/MinaProtocol/mina.git
cd mina/src/app/rosetta/examples/ts
npm install
cp env.example .env # fill in ROSETTA_URL, NETWORK, addresses
npm run account-balance
```

| Script | Page |
| --- | --- |
| `account-balance.ts` | smoke test for `/account/balance` |
| `scan-blocks.ts` | [Scanning Blocks](scan-blocks) |
| `track-deposits.ts` | [Tracking Deposits](track-deposits) |
| `send-transaction.ts`, `offline-sign.ts` | [Sending Transactions](send-transactions) |

## Why no TypeScript SDK?

There is no upstream Coinbase-blessed TypeScript Rosetta/Mesh SDK — the only one they listed (`dfinity/rosetta-client`) was archived in August 2025. Mina ships its own at [`@o1-labs/mina-rosetta-sdk`](https://www.npmjs.com/package/@o1-labs/mina-rosetta-sdk), focused on the endpoints Mina's Rosetta server actually exposes plus the Mina-specific operation types. For Go integrators, [`coinbase/mesh-sdk-go`](https://github.com/coinbase/mesh-sdk-go) remains the canonical Mesh client.

## Network identifier

All endpoints except `/network/list` require a `network_identifier` parameter. For Mina:

```json
{ "blockchain": "mina", "network": "mainnet" }
```

All endpoints except `/network/list` require a `network_identifier` parameter. The samples include it in each request body.
Replace `mainnet` with `devnet` to point at devnet. The SDK's `RosettaClient` injects the network identifier into every request automatically.

:::tip
Replace `mainnet` with `devnet` if you are testing against a devnet instance.
:::
For the Mina-specific request and response objects (operation types, transfer transaction layout, currency, token IDs), see [Requests and Responses](requests).
13 changes: 10 additions & 3 deletions docs/node-operators/rosetta/samples/requests.mdx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
---
title: Requests and Responses
description: Mina-specific Rosetta request and response objects with curl examples
description: Mina-specific Rosetta request and response objects
---

# Requests and Responses

The Rosetta API specification defines high-level descriptions of request and response objects. Exact JSON layouts differ between blockchains. This page covers Mina-specific objects and shows how to query each endpoint with curl.
The Rosetta API specification defines high-level descriptions of request and response objects. Exact JSON layouts differ between blockchains. This page covers Mina-specific objects.

All examples assume the shell variables from the [Code Samples](/node-operators/rosetta/samples) setup.
For the runnable examples that exercise these endpoints, see the [TypeScript scripts](/node-operators/rosetta/samples) in the mina repo. The curl snippets below are illustrative — copy them for quick exploration, but adapt the runnable scripts for production code.

Set these shell variables to follow along:

```bash
ROSETTA_URL="http://localhost:3087"
NETWORK='{"blockchain":"mina","network":"mainnet"}'
```

## Network endpoints

Expand Down
59 changes: 24 additions & 35 deletions docs/node-operators/rosetta/samples/scan-blocks.mdx
Original file line number Diff line number Diff line change
@@ -1,48 +1,37 @@
---
title: Scanning Blocks
description: Poll for new blocks and inspect transactions using curl
description: Poll Mina Rosetta for new blocks and inspect their transactions
---

# Scanning Blocks

To poll for new blocks, query `/network/status` for the current block height, then fetch each block sequentially.
The pattern: query `/network/status` for the current chain tip, then fetch each block by index sequentially. When `/block` returns `{ block: null }` the height is not yet produced — wait and retry.

Get the current block height:
This is the foundation of every other read-side integration (deposit tracking, search, custodial reconciliation). The full runnable version is at [`src/app/rosetta/examples/ts/scan-blocks.ts`](https://github.com/MinaProtocol/mina/blob/develop/src/app/rosetta/examples/ts/scan-blocks.ts). The core loop:

```bash
curl -s "$ROSETTA_URL/network/status" \
-H 'Content-Type: application/json' \
-d "{\"network_identifier\":$NETWORK}" | jq '.current_block_identifier.index'
```ts
let height = (await client.networkStatus()).current_block_identifier.index;

while (true) {
const { block } = await client.block({ index: height });
if (!block) {
await sleep(POLL_INTERVAL_MS);
continue;
}
for (const tx of block.transactions) {
console.log(tx.transaction_identifier.hash);
}
height += 1;
}
```

Fetch a specific block by index:
## Mina-specific notes

```bash
BLOCK_INDEX=1000
- **Block index vs hash**: `/block` accepts either `{ index: number }` or `{ hash: string }`. Index is simpler for sequential scanning; hash is useful when reconciling against an external source.
- **Reorgs**: Mina has a finite reorg window. If you build state from blocks before final confirmation, persist the parent hash and detect divergence — when the new block at height `N` doesn't reference the hash you saw at height `N-1`, walk back and reapply.
- **Confirmations**: see [Lifecycle of a Payment](/mina-protocol/lifecycle-of-a-payment) for how many slots to wait before treating a transaction as final.
- **Skipped slots**: Mina has empty slots (no block produced). If you only see a `null` response, you may need to skip the slot or wait for the next block, depending on whether you index by block height or slot.

curl -s "$ROSETTA_URL/block" \
-H 'Content-Type: application/json' \
-d "{\"network_identifier\":$NETWORK,\"block_identifier\":{\"index\":$BLOCK_INDEX}}" | jq .
```
## Want it in shell?

A simple polling loop that waits for new blocks:

```bash
HEIGHT=$(curl -s "$ROSETTA_URL/network/status" \
-H 'Content-Type: application/json' \
-d "{\"network_identifier\":$NETWORK}" | jq '.current_block_identifier.index')

while true; do
BLOCK=$(curl -s "$ROSETTA_URL/block" \
-H 'Content-Type: application/json' \
-d "{\"network_identifier\":$NETWORK,\"block_identifier\":{\"index\":$HEIGHT}}")

if echo "$BLOCK" | jq -e '.block' > /dev/null 2>&1; then
echo "Block $HEIGHT:"
echo "$BLOCK" | jq '.block.transactions[] | .transaction_identifier.hash'
HEIGHT=$((HEIGHT + 1))
else
sleep 10
fi
done
```
A curl-based loop is in the mina repo at [`src/app/rosetta/test-curl/`](https://github.com/MinaProtocol/mina/tree/develop/src/app/rosetta/test-curl). Useful for exploration; not intended as production code.
171 changes: 35 additions & 136 deletions docs/node-operators/rosetta/samples/send-transactions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,154 +6,53 @@ description: Build, sign, and submit a MINA transfer using the Rosetta Construct
# Sending Transactions

:::info
This flow follows the [Construction API Overview](https://docs.cloud.coinbase.com/rosetta/docs/construction-api-overview) from the official Rosetta documentation.
This page covers the Mina-specific bits. For generic Construction API behavior see the [Mesh Construction API overview](https://docs.cdp.coinbase.com/mesh/docs/construction-api-overview).
:::

The steps to send a MINA payment:
The full Construction API flow:

1. Derive the account address from a public key
2. Build the unsigned transaction via preprocess → metadata → payloads
3. Sign offline with the [signer tool](/node-operators/mina-signer)
4. Combine the signature into a signed blob
5. Submit the signed transaction
1. **preprocess** — given the operations, return options the metadata call needs
2. **metadata** — given those options, return suggested fee, nonce, etc.
3. **payloads** — given operations + metadata, return an unsigned transaction and the bytes to sign
4. **sign** — sign the bytes offline (Pallas curve)
5. **combine** — package the unsigned transaction with signatures into a signed transaction blob
6. **submit** — broadcast the signed transaction

## Prerequisites
For Mina, [`mina-signer`](https://www.npmjs.com/package/mina-signer) handles step 4 plus the formatting of step 5's input. The `rosettaCombinePayload(payloadsResponse, privateKey)` helper takes the result of `/construction/payloads` and produces the body for `/construction/combine` directly.

- A key pair generated with the [offline signer tool](/node-operators/mina-signer)
- The account must have a balance (send test funds on devnet first)
- Set the shell variables from the [Code Samples](/node-operators/rosetta/samples) setup
The full runnable version is at [`src/app/rosetta/examples/ts/send-transaction.ts`](https://github.com/MinaProtocol/mina/blob/develop/src/app/rosetta/examples/ts/send-transaction.ts). Core flow:

Set your keys and transfer parameters:
```ts
const operations = buildTransferOperations({ sender, receiver, amountNanomina, feeNanomina });

```bash
PUBLIC_KEY="YOUR_PUBLIC_KEY_HEX"
SENDER="B62q..."
RECEIVER="B62q..."
TOKEN_ID="wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"
FEE=10000000 # 0.01 MINA in nanomina
VALUE=1000000000 # 1 MINA in nanomina
```

## Step 1: Derive account address

```bash
curl -s "$ROSETTA_URL/construction/derive" \
-H 'Content-Type: application/json' \
-d "{
\"network_identifier\":$NETWORK,
\"public_key\":{\"hex_bytes\":\"$PUBLIC_KEY\",\"curve_type\":\"pallas\"}
}" | jq .
```

## Step 2: Build the operations payload

Construct the three operations that represent a MINA transfer (see [Requests and Responses](requests#transfer-transaction-layout) for details on the structure):

```bash
OPERATIONS='[
{
"operation_identifier":{"index":0},
"type":"fee_payment",
"account":{"address":"'"$SENDER"'","metadata":{"token_id":"'"$TOKEN_ID"'"}},
"amount":{"value":"-'"$FEE"'","currency":{"symbol":"MINA","decimals":9}}
},
{
"operation_identifier":{"index":1},
"type":"payment_source_dec",
"account":{"address":"'"$SENDER"'","metadata":{"token_id":"'"$TOKEN_ID"'"}},
"amount":{"value":"-'"$VALUE"'","currency":{"symbol":"MINA","decimals":9}}
},
{
"operation_identifier":{"index":2},
"related_operations":[{"index":1}],
"type":"payment_receiver_inc",
"account":{"address":"'"$RECEIVER"'","metadata":{"token_id":"'"$TOKEN_ID"'"}},
"amount":{"value":"'"$VALUE"'","currency":{"symbol":"MINA","decimals":9}}
}
]'
```

## Step 3: Preprocess

```bash
PREPROCESS=$(curl -s "$ROSETTA_URL/construction/preprocess" \
-H 'Content-Type: application/json' \
-d "{\"network_identifier\":$NETWORK,\"operations\":$OPERATIONS}")

echo "$PREPROCESS" | jq .
```

## Step 4: Metadata

```bash
METADATA=$(curl -s "$ROSETTA_URL/construction/metadata" \
-H 'Content-Type: application/json' \
-d "$(echo "$PREPROCESS" | jq -c ". + {
\"network_identifier\":$NETWORK,
\"public_keys\":[{\"hex_bytes\":\"$PUBLIC_KEY\",\"curve_type\":\"pallas\"}]
}")")
const { options } = await rosetta.constructionPreprocess(operations);
const { metadata } = await rosetta.constructionMetadata(options, publicKeys);
const payloadsResponse = await rosetta.constructionPayloads(operations, metadata, publicKeys);

echo "$METADATA" | jq .
const combine = signer.rosettaCombinePayload(payloadsResponse, privateKey);
const { signed_transaction } = await rosetta.constructionCombine(
combine.unsigned_transaction,
combine.signatures,
);
const result = await rosetta.constructionSubmit(signed_transaction);
```

## Step 5: Payloads (unsigned transaction)
## Cold-signing setup

```bash
PAYLOADS=$(curl -s "$ROSETTA_URL/construction/payloads" \
-H 'Content-Type: application/json' \
-d "$(echo "$METADATA" | jq -c ". + {
\"network_identifier\":$NETWORK,
\"operations\":$OPERATIONS
}")")
For exchanges that keep signing keys on an air-gapped machine, the Construction API splits naturally across the network boundary:

echo "$PAYLOADS" | jq .
UNSIGNED_TX=$(echo "$PAYLOADS" | jq -r '.unsigned_transaction')
```

## Step 6: Sign offline

Use the [signer CLI tool](/node-operators/mina-signer#signing-a-transaction-with-signer-cli):

```bash
SIGNATURE=$(signer sign --private-key "$PRIVATE_KEY" --unsigned-transaction "$UNSIGNED_TX")
```

## Step 7: Combine

```bash
SIGNING_PAYLOAD=$(echo "$PAYLOADS" | jq -c '.payloads[0]')
- **Online host** runs preprocess → metadata → payloads, persists the response
- **Offline host** reads the persisted payload, signs it with `mina-signer`, persists the result
- **Online host** reads the signed result, calls combine → submit

COMBINE=$(curl -s "$ROSETTA_URL/construction/combine" \
-H 'Content-Type: application/json' \
-d "{
\"network_identifier\":$NETWORK,
\"unsigned_transaction\":\"$UNSIGNED_TX\",
\"signatures\":[{
\"signing_payload\":$SIGNING_PAYLOAD,
\"public_key\":{\"hex_bytes\":\"$PUBLIC_KEY\",\"curve_type\":\"pallas\"},
\"signature_type\":\"schnorr_poseidon\",
\"hex_bytes\":\"$SIGNATURE\"
}]
}")
See [`src/app/rosetta/examples/ts/offline-sign.ts`](https://github.com/MinaProtocol/mina/blob/develop/src/app/rosetta/examples/ts/offline-sign.ts) for the same flow split across the boundary, with on-disk handoff files standing in for the network gap.

SIGNED_TX=$(echo "$COMBINE" | jq -r '.signed_transaction')
echo "$COMBINE" | jq .
```

## Step 8: Get transaction hash

```bash
curl -s "$ROSETTA_URL/construction/hash" \
-H 'Content-Type: application/json' \
-d "{\"network_identifier\":$NETWORK,\"signed_transaction\":\"$SIGNED_TX\"}" | jq .
```

## Step 9: Submit

```bash
curl -s "$ROSETTA_URL/construction/submit" \
-H 'Content-Type: application/json' \
-d "{\"network_identifier\":$NETWORK,\"signed_transaction\":\"$SIGNED_TX\"}" | jq .
```
## Mina-specific notes

After submission, you can monitor for confirmation using the [block scanning](scan-blocks) approach — poll blocks until your transaction hash appears.
- **Three operations per transfer**: `fee_payment` (sender pays fee), `payment_source_dec` (sender decrement), `payment_receiver_inc` (receiver increment). Build them with the SDK's `buildTransferOperations()` helper or see the [transfer transaction layout](requests#transfer-transaction-layout).
- **Curve type is `pallas`**, not `secp256k1` or `edwards25519` as in some other chains.
- **Signature type is `schnorr_poseidon`**.
- **Amounts are nanomina** strings: 1 MINA = 1,000,000,000 nanomina. Negative values represent debits (fee, source decrement).
- **Token ID defaults to `wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf`** for MINA. Set this on every operation's `account.metadata.token_id`.
- **Account creation fee**: if the receiver address has never been used, a 1 MINA account creation fee is charged automatically. Surface this in your UI.
- **Confirmations**: monitor by [scanning blocks](scan-blocks) for the returned `transaction_identifier.hash`. See [Lifecycle of a Payment](/mina-protocol/lifecycle-of-a-payment) for how many slots to wait.
Loading
Loading