Skip to main content

Prerequisites

  • Go 1.25+
  • Docker + Docker Compose
  • CometBFT if you want to run the current reference node
go version   # >= go1.25
docker --version

1. Get the modules

The current transfer scaffold still shares consensus and signing infrastructure with the broader shyware runtime.
go get github.com/co-mission/shyware@latest
go get github.com/populist/protocol@latest

2. Start a local node

# Initialize CometBFT config + genesis
cometbft init --home ~/.shyware-dev

# Start the transfer app (development mode — local signer, no managed key service)
go run ./cmd/shyware-abci --home ~/.shyware-dev

# In a separate terminal, start the consensus node
cometbft start --home ~/.shyware-dev --proxy_app tcp://127.0.0.1:26658
The API server starts on :8080 by default.

3. Register an asset

curl -X POST http://localhost:8080/assets \
  -H "Content-Type: application/json" \
  -d '{
    "type": 1,
    "data": {
      "asset_id": "usdc",
      "name": "USD Coin",
      "decimals": 6
    }
  }'

4. Register two accounts

Accounts are identified by account_commitment = H(wallet_address). The wallet address never appears on-chain.
# Account A
COMMITMENT_A=$(echo -n "wallet_address_alice" | sha256sum | awk '{print $1}')
curl -X POST http://localhost:8080/accounts \
  -H "Content-Type: application/json" \
  -d "{\"account_commitment\": \"$COMMITMENT_A\", \"wallet_proof\": \"<ECDSA_SIG>\"}"

# Account B
COMMITMENT_B=$(echo -n "wallet_address_bob" | sha256sum | awk '{print $1}')
curl -X POST http://localhost:8080/accounts \
  -H "Content-Type: application/json" \
  -d "{\"account_commitment\": \"$COMMITMENT_B\", \"wallet_proof\": \"<ECDSA_SIG>\"}"

5. Mint supply to Account A

curl -X POST http://localhost:8080/mint \
  -H "Content-Type: application/json" \
  -d "{
    \"asset_id\": \"usdc\",
    \"account_commitment\": \"$COMMITMENT_A\",
    \"amount\": 1000000
  }"
Verify supply:
curl http://localhost:8080/supply/usdc
# {"asset_id":"usdc","total_minted":1000000,"total_burned":0,"total_supply":1000000}

6. Execute an anonymous transfer

Generate a random transfer_nonce, derive transfer_id = H(nonce), and compute nullifier = H(sender_wallet, transfer_id).
NONCE=$(openssl rand -hex 32)
TRANSFER_ID=$(echo -n "$NONCE" | sha256sum | awk '{print $1}')
NULLIFIER=$(echo -n "wallet_address_alice$TRANSFER_ID" | sha256sum | awk '{print $1}')

curl -X POST http://localhost:8080/transfers \
  -H "Content-Type: application/json" \
  -d "{
    \"asset_id\": \"usdc\",
    \"sender_commitment\": \"$COMMITMENT_A\",
    \"recipient_commitment\": \"$COMMITMENT_B\",
    \"amount\": 100000,
    \"nullifier\": \"$NULLIFIER\",
    \"transfer_nonce\": \"$NONCE\"
  }"
The response returns the transfer_id. This is the public-facing receipt token the sender holds.

7. Verify the invariant holds

# List 1: transfer count
curl http://localhost:8080/transfers/count
# {"count": 1}

# List 2: participant count (should equal List 1 count)
curl http://localhost:8080/participants/count
# {"count": 1}

# Supply unchanged (conservation)
curl http://localhost:8080/supply/usdc
# {"total_supply": 1000000}
|L1| == |L2| and total supply is conserved. The transfer is in canonical state.

Next steps