| name | zkool-graphql-regtest |
| description | Set up a Zkool GraphQL server on Zcash regtest, restore a faucet account, create a test account, and fund it. |
Zkool GraphQL Regtest Setup
Spin up a Zkool GraphQL Docker container, restore a faucet account from seed, create a test account, and send test funds.
Prerequisites
- Docker running (Colima or Docker Desktop)
- Docker Hub image
hhanh00/zkool-graphql:latest
- Faucet seed phrase
Workflow
1. Pull the image
docker pull hhanh00/zkool-graphql:latest
2. Run the container
docker rm -f zkool-graphql 2>/dev/null
docker run -d \
--name zkool-graphql \
-v zkool-data:/data \
-p 8080:8080 \
hhanh00/zkool-graphql:latest \
--db-path /data/zsa-regtest \
--lwd-url https://zsa.methyl.cc \
--port 8080
Verify:
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/
3. Restore the Faucet account
curl -s -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation($new: NewAccount!) { createAccount(newAccount: $new) }",
"variables": {
"new": {
"name": "Faucet",
"key": "equal clock rain latin plastic toss scrub modify clarify fold armor exchange gesture erase habit plug state forward demise demand limb risk only document",
"passphrase": "",
"aindex": 0,
"birth": 5,
"pools": 7,
"useInternal": false
}
}
}'
Pool bitmask: 1=transparent, 2=sapling, 4=orchard. 7 = all pools.
Verify and get addresses:
curl -s -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ accounts { id name seed aindex dindex birth height balance } addressByAccount(idAccount: 1) { transparent sapling orchard ua } }"}'
4. Sync the Faucet
curl -s -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation($id: Int!) { synchronizeAccount(idAccount: $id, fast: false) }",
"variables": { "id": 1 }
}'
Check balance:
curl -s -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ balanceByAccount(idAccount: 1) { height transparent sapling orchard total } }"}'
5. Create a test account
curl -s -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation($new: NewAccount!) { createAccount(newAccount: $new) }",
"variables": {
"new": {
"name": "<Test Account Name>",
"key": "",
"passphrase": "",
"aindex": 0,
"birth": 5,
"pools": 7,
"useInternal": false
}
}
}'
Get the test account's transparent address:
curl -s -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ addressByAccount(idAccount: <ACCOUNT_ID>) { transparent } }"}'
6. Send funds from Faucet to test account
curl -s -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation($id: Int!, $pay: Payment!) { pay(idAccount: $id, payment: $pay) }",
"variables": {
"id": <FAUCET_ID>,
"pay": {
"recipients": [{
"address": "<TEST_TRANSPARENT_ADDRESS>",
"amount": "1"
}],
"srcPools": <POOL_BITMASK>
}
}
}'
Returns a txid (string).
7. Check transaction confirmation
curl -s -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ unconfirmedByAccount(idAccount: <ACCOUNT_ID>) { txid } }"}'
curl -s -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ transactionsByAccount(idAccount: <ACCOUNT_ID>, height: 0) { id txid height } }"}'
curl -s -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ balanceByAccount(idAccount: <ACCOUNT_ID>) { height transparent sapling orchard total } }"}'
Regtest note: Transactions sit in the mempool until the node mines a block that includes them. This may take a few minutes.
ZSA Transfer Troubleshooting
Common Issues and Solutions
1. Wrong assetDesc Format
Error: Expected input scalar String. Got: {idAsset: 7}
Problem: Passing assetDesc as an object instead of a string
Solution: assetDesc must be a string value
2. Ambiguous Asset Reference
Error: Ambiguous query '7', matches 2 assets
Problem: Using asset ID instead of full descriptor hash
Solution: Always use the full asset descriptor hash, not just the ID
3. Insufficient Funds / Address Type Mismatch
Error: Insufficient funds for transaction construction; need an additional ZatBalance(X) zatoshis
Problem: Trying to send ZSA to incompatible address type (e.g., transparent address for Orchard-based assets)
Solution: Use compatible address types:
- Orchard-based ZSA → Orchard or unified address
- Sapling-based ZSA → Sapling or unified address
- Transparent-based ZSA → Transparent address
Critical Requirements for ZSA Transfers
- Asset Descriptor Hash: Always use the full
assetDescHash (64-character hex string), never just the idAsset number
- Address Type Compatibility: ZSA assets can only be sent to compatible address types:
- Orchard pool assets → Orchard addresses (
uregtest1...)
- Sapling pool assets → Sapling addresses (
zregtestsapling1...)
- Transparent pool assets → Transparent addresses (
tmX...)
- Unified addresses work for all types
Example: Successful ZSA Transfer
curl -s -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation($id: Int!, $pay: Payment!) { pay(idAccount: $id, payment: $pay) }",
"variables": {
"id": <SENDER_ACCOUNT_ID>,
"pay": {
"recipients": [{
"address": "<ORCHARD_OR_UNIFIED_ADDRESS>",
"amount": "<AMOUNT>",
"assetDesc": "<FULL_ASSET_DESCRIPTOR_HASH>"
}],
"srcPools": 4
}
}
}'
Key points:
- Use full asset descriptor hash (64-char hex)
- Use Orchard/unified address for Orchard-based assets
srcPools: 4 for Orchard-based assets
GraphQL API Reference
Queries
| Query | Description |
|---|
accounts(accountFilter) | List accounts |
balanceByAccount(idAccount, height) | Get balance breakdown |
addressByAccount(idAccount, pools) | Get addresses per pool |
transactionsByAccount(idAccount, height) | List transactions |
unconfirmedByAccount(idAccount) | Pending transactions |
assets(idAccount) | List issued ZSA assets |
currentHeight | Latest chain height |
Mutations
| Mutation | Returns | Description |
|---|
createAccount(newAccount) | Int | Restore an account (returns ID) |
deleteAccount(idAccount) | Boolean | Delete an account |
editAccount(idAccount, updateAccount) | Boolean | Update name/birth |
synchronizeAccount(idAccount, fast) | Int | Sync to chain tip |
pay(idAccount, payment) | String | Send ZEC (returns txid) |
issueAsset(idAccount, assetName, amount, firstIssuance, finalize) | String | Issue a ZSA asset |
setAssetName(idAsset, name) | String | Rename asset |
newAddresses(idAccount) | Addresses | Generate new addresses |
Input Types
NewAccount: name!, key! (seed), passphrase, aindex! (usually 0), birth, pools (bitmask), useInternal!
Payment: recipients! (list), srcPools (bitmask), recipientPaysFee, confirmations
Recipient: address!, amount! (string in ZEC), memo, assetDesc (string - must use full asset descriptor hash for ZSA transfers, not asset ID)
UpdateAccount: name, birth
Safety Notes
- Server runs without authentication — restrict to localhost/LAN.
- Seed phrases stored in plaintext in the container's database.
- Transparent addresses only generated when
pools includes 1.
Remarks
- When polling for transaction confirmation, synchronize the receiving account to update its balance and transaction history.
- Test account with some ZSA: "poet key eye depart surprise cloth jaguar brisk tackle husband horse upset goddess prize chimney vote grief wall index strong common video wool host"