Build on SORA 3: Taira and Minamoto
SORA 3 is the app-facing public deployment track built on Iroha 3 and SORA Nexus. Build and rehearse on Taira first, then move the same client shape to Minamoto only when you have separate mainnet keys, real XOR for fees, and production approval.
This tutorial shows how to configure an Iroha client for the public SORA 3 networks:
- Taira testnet at
https://taira.sora.org - Minamoto mainnet at
https://minamoto.sora.org
Use Taira for integration tests, faucet-funded write canaries, and deployment rehearsals. Use Minamoto only for production-ready mainnet activity. Both networks charge fees in XOR:
- Taira uses testnet XOR from the public faucet.
- Minamoto uses real XOR. There is no Minamoto faucet.
Builder Path
| Step | Taira Testnet | Minamoto Mainnet |
|---|---|---|
| Start reading network state | Query /status without keys | Query /status without keys |
| Pick a dataspace | Use public universal unless your app needs a governed lane | Use the same dataspace only after mainnet approval |
| Get fee asset | Use the public Taira faucet | Obtain real XOR, then claim it into Minamoto |
| Test writes | Use faucet-funded test XOR | Do not use test tooling; writes spend real XOR |
| Promote | Keep retry logic, monitoring, and signer handling | Use separate keys, funding, and release controls |
The practical flow is:
- Build the client against Taira and use the public
universaldataspace. - Add a signer and fund it with the Taira faucet.
- Exercise your app logic against Taira until failures are boring and observable.
- Create a separate Minamoto signer, fund it with real XOR, and move only the same proven operations to mainnet.
1. Understand What You Are Setting Up
In SORA Nexus, a dataspace is part of the network lane and routing catalog. A client does not create a new public dataspace just by changing client.toml. Client setup does two things:
- points the client at the right Torii endpoint
- selects an account domain that belongs to an existing dataspace
For most applications, start with the public universal dataspace. Account domains use domain.dataspace form, for example:
wonderland.universalIf you need a new organizational dataspace, prepare a catalog and routing proposal instead of trying to register it from an ordinary client account. See Provision a New Dataspace below.
2. Check the Public Torii Endpoint
Check that the target endpoint is live before configuring a signer.
For Taira:
curl -fsS https://taira.sora.org/status \
| jq '{peers, blocks, txs_approved, queue_size}'For Minamoto:
curl -fsS https://minamoto.sora.org/status \
| jq '{peers, blocks, txs_approved, queue_size}'Inspect the dataspace and lane view exposed by the node:
curl -fsS https://taira.sora.org/status \
| jq '.teu_lane_commit[] | {lane_id, alias, dataspace_id, dataspace_alias, visibility}'Use the same command with https://minamoto.sora.org/status for mainnet.
Taira MCP for Agents
Taira also exposes a Torii-native Model Context Protocol (MCP) bridge for agent runtimes. Use it when an agent needs live testnet reads, scripted diagnostics, or tightly reviewed write rehearsals without building a custom Torii client first.
| Setting | Value |
|---|---|
| MCP endpoint | https://taira.sora.org/v1/mcp |
| Network root | https://taira.sora.org |
| Intended use | Taira testnet reads and faucet-funded write rehearsals |
| Production equivalent | Do not point this entry at Minamoto unless a mainnet MCP endpoint and release controls are explicitly approved |
Check the bridge metadata before adding signing material:
curl -fsS https://taira.sora.org/v1/mcp \
| jq '{protocolVersion, server: .serverInfo.name, tools: .capabilities.tools.count}'Configure the URL as a user-local MCP server in the agent runtime. Do not commit agent MCP config, API tokens, forwarded auth headers, authority, or private_key values into this docs repo or an application repo.
Agent prompt rules that work well with Taira:
- Discover tools from the MCP server before calling them; re-discover if the server reports
listChanged. - Prefer the curated
iroha.*tools over rawtorii.*tools. - Start read-only: inspect status, accounts, assets, aliases, blocks, governance state, and transaction status before proposing writes.
- Require an explicit human instruction before live testnet mutations. For pre-signed transaction envelopes, use
iroha.transactions.submit_and_waitso the agent waits for the result instead of only submitting. - Summarize transaction hashes, final status, and server validation errors in the agent response.
Development Workflow With Agents
Use agents as development helpers for Iroha clients, transaction builders, diagnostic scripts, and testnet runbooks. Keep the agent's authority narrow: it can inspect code, read Taira state, propose changes, and run local tests, but it should not mutate a live network until a human approves the exact operation.
A practical workflow is:
- Ask the agent to inspect the relevant docs, SDK code, CLI command, or MCP tool schema before it writes code.
- Have the agent write the smallest client path first: status check, account lookup, alias resolution, or balance lookup.
- Add transaction-building code only after read-only calls work against Taira.
- Keep live-network tests opt-in, for example behind
TAIRA_LIVE=1, so a normal unit test run never spends testnet funds or depends on network availability. - Require the agent to report the network root, chain, authority account, instruction summary, fee asset, and expected state change before it submits any transaction.
- Review generated code for secret handling, retry behavior, idempotency, and rejection handling before promoting it to CI or mainnet workflows.
Useful read-only MCP tools for development include account asset lookups, alias resolution, block lookup, transaction lookup, transaction lists, and pipeline status checks. Use these to build confidence before submitting any signed payload.
Use Taira MCP as a read-only inspector while developing this Iroha feature.
Inspect available iroha.* tools, verify the target account and asset state,
then update the client code. Do not submit transactions unless I explicitly
say "submit this transaction".Transaction Workflow Through Agents
The MCP bridge can submit a signed Iroha transaction, but it does not remove the normal transaction requirements. A transaction still needs a correct authority, permissions, fee funding, chain ID, metadata, and signature.
For raw Iroha transactions, build and sign the transaction envelope with an SDK or CLI first, then give the agent only the canonical signed transaction bytes encoded as body_base64. The agent can submit the envelope with iroha.transactions.submit_and_wait, or submit with iroha.transactions.submit and poll with iroha.transactions.wait.
Do not paste private keys into an agent prompt. If an agent needs to build a transaction, point it at local code that loads secrets from the user's runtime environment, keychain, hardware signer, or ignored testnet config file. The agent should never write the key material into Markdown, fixtures, logs, or commits.
Before submitting a transaction, make the agent produce a short transaction plan:
network: Taira testnet root and chain IDauthority: account that signs and pays feesinstructions: register, mint, burn, transfer, metadata, permission, or contract call summaryfee asset: asset that will be charged on Tairapreflight reads: account, asset balance, permissions, alias, or block checks already performedexpected result: the state that should be visible after confirmationidempotency: what happens if the same request is retried
After submission, make the agent wait for a terminal status, then verify the state change with a read query. A useful completion report includes:
- transaction hash
- terminal status such as
Committed,Applied,Rejected, orExpired - block or explorer detail when available
- verification read results
- rejection message and whether the failure looks like permissions, fees, validation, stale state, or endpoint availability
Example guarded prompt:
Prepare a Taira transaction plan, but do not submit yet. Use MCP reads to
verify the authority account, fee balance, target asset or alias, and current
transaction status if a hash already exists. Show the exact instructions and
expected post-state. Wait for my explicit "submit" message before calling
iroha.transactions.submit_and_wait.When the signed envelope is already prepared:
Submit this pre-signed Taira transaction envelope with
iroha.transactions.submit_and_wait. Use the provided body_base64 only; do not
ask for private keys. Wait for a terminal status, then verify the resulting
state with read-only iroha.* tools and report the hash, status, and
verification result.Treat Taira MCP as a public testnet control surface. Taira keys, testnet XOR, faucet accounts, and canary signers are disposable and must stay separate from Minamoto keys and production release workflows.
Toy Examples You Can Try Now
These examples are read-only unless noted. They work before you generate keys and are safe to run against both public networks.
Compare Taira testnet and Minamoto mainnet health:
for network in taira minamoto; do
root="https://$network.sora.org"
printf '\n%s\n' "$network"
curl -fsS "$root/status" \
| jq '{blocks, txs_approved, txs_rejected, queue_size, peers}'
doneList the public dataspace lanes exposed by Taira:
curl -fsS https://taira.sora.org/status \
| jq -r '.teu_lane_commit[]
| [.lane_id, .alias, .dataspace_alias, .visibility, .storage_profile, .block_height]
| @tsv'Run the same command against Minamoto when you need the mainnet view:
curl -fsS https://minamoto.sora.org/status \
| jq -r '.teu_lane_commit[]
| [.lane_id, .alias, .dataspace_alias, .visibility, .storage_profile, .block_height]
| @tsv'Build a tiny Node.js status probe for a dashboard, bot, or deployment check:
node --input-type=module <<'EOF'
const roots = {
taira: 'https://taira.sora.org',
minamoto: 'https://minamoto.sora.org',
};
for (const [name, root] of Object.entries(roots)) {
const status = await fetch(`${root}/status`).then((res) => res.json());
const publicSpaces = status.teu_lane_commit
.filter((lane) => lane.visibility === 'public')
.map((lane) => `${lane.dataspace_alias}:${lane.block_height}`)
.join(', ');
console.log(
`${name}: ${status.blocks} blocks, ${status.queue_size} queued, public spaces ${publicSpaces}`,
);
}
EOFThe first write-side toy should be a Taira faucet claim. It uses testnet XOR and should never be pointed at Minamoto.
3. Create a Taira Client Config
Generate a keypair if you do not already have one:
kagami keys --algorithm ed25519 --jsonCreate taira.client.toml:
chain = "809574f5-fee7-5e69-bfcf-52451e42d50f"
torii_url = "https://taira.sora.org/"
[account]
domain = "wonderland.universal"
public_key = "<ED25519_PUBLIC_KEY_HEX>"
private_key = "<ED25519_PRIVATE_KEY_HEX>"
chain_discriminant = 369
[transaction]
time_to_live_ms = 100000
status_timeout_ms = 15000
nonce = falseRecent CLI builds infer the Taira chain_discriminant from the chain value or from taira.sora.org, but keeping it explicit makes the config portable across older builds.
Run a read-only check:
iroha --config ./taira.client.toml --output-format text ops sumeragi statusRun the public Taira diagnostics before write tests:
iroha taira doctor --public-root https://taira.sora.org --jsonFund the Taira account through the faucet before you run fee-paying writes. The direct faucet flow is in Get Testnet XOR on Taira.
After the faucet claim is accepted and the account is funded, the Taira canary is an optional write smoke test:
iroha --config ./taira.client.toml taira write-canary \
--public-root https://taira.sora.org \
--write-config ./taira.canary.client.toml \
--jsonThe canary submits a signed ping, waits for confirmation, and writes the runtime signer config when --write-config is provided. Taira is a public testnet, so queue saturation can make the signed ping fail even when the faucet itself works. If taira doctor reports a saturated queue or the canary returns PRTRY:NEXUS_FEE_ADMISSION_REJECTED, wait and retry before treating it as a client configuration error.
For unattended smoke tests, wrap the canary in a bounded retry loop:
ok=false
for attempt in 1 2 3 4 5; do
iroha --config ./taira.client.toml taira write-canary \
--public-root https://taira.sora.org \
--write-config ./taira.canary.client.toml \
--json && ok=true && break
sleep 60
done
test "$ok" = trueStop retrying if iroha taira doctor shows hard failures. Queue saturation and fee-admission rejections are transient public-testnet conditions; DNS, TLS, or status = "fail" diagnostics are not.
Generate a SORA Nexus Account ID
A SORA Nexus account ID is a canonical I105 address derived from the account public key and the target network prefix. It is not the [account].domain value in client TOML. The same public key encodes to different IDs on Taira and Minamoto, and production users should generate a separate keypair for Minamoto.
Generate or load the Ed25519 keypair that will control the account:
kagami keys --algorithm ed25519 --jsonConvert the public key into a Taira account ID:
iroha tools address convert --network-prefix 369 <ED25519_PUBLIC_KEY_HEX>Convert a Minamoto public key with the mainnet prefix:
iroha tools address convert --network-prefix 753 <ED25519_PUBLIC_KEY_HEX>Use the resulting account ID wherever a Nexus API or CLI command asks for a canonical account ID, for example the Taira faucet account_id, balance queries, strict account fields, or alias bindings. Keep the matching private key in your client config, and keep chain_discriminant aligned with the same prefix: 369 for Taira and 753 for Minamoto.
Generating the ID does not by itself create a funded on-chain account. On Taira, the faucet can create and fund the account for testnet writes. On Minamoto, use the approved mainnet onboarding, claim, or treasury flow.
Key Storage and Backup
The account ID and public key can be shared. The matching private key, passphrase, seed, and recovery material must be treated as secret.
Use these practices for SORA Nexus accounts:
- Store private keys in an encrypted password manager, hardware-backed keystore, or dedicated signing service. Do not commit keys to source control or leave production keys in shell history, logs, chat, tickets, or unencrypted backups.
- Use a unique high-entropy passphrase for each vault or production signer. Store passphrases in a password manager or split custody process, not in the same file or backup bundle as the encrypted private key.
- Keep Taira and Minamoto keys separate. Treat Taira keys as disposable testnet material and Minamoto keys as production funds authority.
- Back up the private key, public key, account ID, network prefix,
chain_discriminant, and any account recovery or custody notes needed to restore the signer. A private key without the network context is easy to misuse during recovery. - Keep at least one encrypted offline backup and one geographically separate encrypted backup for production signers. Test recovery with a small read-only operation before depending on the backup.
- Rotate or replace a signer if the private key, passphrase, backup media, or signing host may have been exposed.
For more detail, see Storing Cryptographic Keys and Password Security.
4. Get Testnet XOR on Taira
Use the public faucet directly. The flow is:
- Generate or load a signer and compute its canonical Taira account ID.
- Fetch the current faucet puzzle.
- Solve the puzzle if
difficulty_bitsis greater than0. - Submit the faucet claim.
- Wait for the account or asset balance to become visible before sending fee-paying writes.
Convert a public key into the Taira I105 account ID expected by the faucet:
iroha tools address convert --network-prefix 369 <ED25519_PUBLIC_KEY_HEX>Fetch the puzzle:
curl -fsS https://taira.sora.org/v1/accounts/faucet/puzzle | jq .The faucet is a public testnet service. If the puzzle or claim endpoint returns 502, a timeout, or another gateway-level error, wait and retry before changing your keys or client config.
The response has this shape:
{
"algorithm": "scrypt-leading-zero-bits-v1",
"difficulty_bits": 8,
"anchor_height": 741,
"anchor_block_hash_hex": "05d2...",
"challenge_salt_hex": null,
"scrypt_log_n": 13,
"scrypt_r": 8,
"scrypt_p": 1,
"max_anchor_age_blocks": 6
}When difficulty_bits is 0, submit only the account ID:
curl -fsS https://taira.sora.org/v1/accounts/faucet \
-H 'content-type: application/json' \
-d '{"account_id":"<TAIRA_I105_ACCOUNT_ID>"}'When difficulty_bits is greater than 0, solve the puzzle and include the anchor height plus nonce:
curl -fsS https://taira.sora.org/v1/accounts/faucet \
-H 'content-type: application/json' \
-d '{
"account_id": "<TAIRA_I105_ACCOUNT_ID>",
"pow_anchor_height": 741,
"pow_nonce_hex": "<NONCE_HEX>"
}'The puzzle algorithm is:
- Build the challenge as SHA-256 over:
- the bytes of
iroha:accounts:faucet:pow:v2 - the UTF-8 account ID
anchor_heightas big-endianu64anchor_block_hash_hexdecoded as byteschallenge_salt_hexdecoded as bytes, when present
- the bytes of
- Try
u64nonces encoded as big-endian 8-byte values. - For each nonce, run scrypt with:
- password: the 8-byte nonce
- salt: the 32-byte challenge
N = 2^scrypt_log_nr = scrypt_rp = scrypt_p- output length: 32 bytes
- The winning nonce is the first digest with at least
difficulty_bitsleading zero bits.
The faucet response includes the funded asset and queued transaction hash:
{
"account_id": "<TAIRA_I105_ACCOUNT_ID>",
"asset_definition_id": "6TEAJqbb8oEPmLncoNiMRbLEK6tw",
"asset_id": "...",
"amount": "25000",
"tx_hash_hex": "...",
"status": "QUEUED"
}The response is currently returned with HTTP 202 Accepted. The asset definition ID above is the Taira fee asset funded by the public faucet. The faucet has accepted the request when it returns tx_hash_hex and status: "QUEUED".
Then poll for the funded asset before submitting your own fee-paying transactions:
iroha --config ./taira.client.toml ledger asset get \
--definition 6TEAJqbb8oEPmLncoNiMRbLEK6tw \
--account <TAIRA_I105_ACCOUNT_ID>If the faucet claim was accepted but the account or asset is not visible yet, the transaction is still behind public testnet queue processing. Wait and retry the read before sending writes.
For a ready-to-run direct API check, save this as taira_faucet_claim.py and pass the Taira I105 account ID:
#!/usr/bin/env python3
import hashlib
import json
import sys
import urllib.request
def has_leading_zero_bits(digest: bytes, bits: int) -> bool:
full, rem = divmod(bits, 8)
if digest[:full] != b"\0" * full:
return False
return rem == 0 or digest[full] >> (8 - rem) == 0
root = "https://taira.sora.org"
account_id = sys.argv[1]
with urllib.request.urlopen(f"{root}/v1/accounts/faucet/puzzle") as res:
puzzle = json.load(res)
claim = {"account_id": account_id}
difficulty = int(puzzle["difficulty_bits"])
if difficulty > 0:
challenge = hashlib.sha256()
challenge.update(b"iroha:accounts:faucet:pow:v2")
challenge.update(account_id.encode())
challenge.update(int(puzzle["anchor_height"]).to_bytes(8, "big"))
challenge.update(bytes.fromhex(puzzle["anchor_block_hash_hex"]))
if puzzle.get("challenge_salt_hex"):
challenge.update(bytes.fromhex(puzzle["challenge_salt_hex"]))
n = 1 << int(puzzle["scrypt_log_n"])
r = int(puzzle["scrypt_r"])
p = int(puzzle["scrypt_p"])
salt = challenge.digest()
for nonce in range(1_000_000):
nonce_bytes = nonce.to_bytes(8, "big")
digest = hashlib.scrypt(nonce_bytes, salt=salt, n=n, r=r, p=p, dklen=32)
if has_leading_zero_bits(digest, difficulty):
claim["pow_anchor_height"] = puzzle["anchor_height"]
claim["pow_nonce_hex"] = nonce_bytes.hex()
break
else:
raise SystemExit("faucet nonce not found")
request = urllib.request.Request(
f"{root}/v1/accounts/faucet",
data=json.dumps(claim).encode(),
headers={"content-type": "application/json"},
method="POST",
)
with urllib.request.urlopen(request) as res:
print(json.dumps(json.load(res), indent=2))The faucet is only for Taira testnet funds. Do not use testnet XOR, faucet accounts, or Taira canary signers in Minamoto flows.
5. Create a Minamoto Client Config
Use a separate keypair for Minamoto. Do not reuse Taira keys for mainnet.
Create minamoto.client.toml:
chain = "00000000-0000-0000-0000-000000000753"
torii_url = "https://minamoto.sora.org/"
[account]
domain = "wonderland.universal"
public_key = "<ED25519_PUBLIC_KEY_HEX>"
private_key = "<ED25519_PRIVATE_KEY_HEX>"
chain_discriminant = 753
[transaction]
time_to_live_ms = 100000
status_timeout_ms = 15000
nonce = falseThe explicit chain_discriminant = 753 is important for Minamoto configs until your CLI or SDK version maps minamoto.sora.org automatically.
Convert a Minamoto public key into its canonical I105 account ID with the mainnet prefix:
iroha tools address convert --network-prefix 753 <ED25519_PUBLIC_KEY_HEX>Run only read-side checks until the account is provisioned and funded through the mainnet onboarding or governance flow:
iroha --config ./minamoto.client.toml --output-format text ops sumeragi statusDo not run the Taira faucet or write-canary helper against Minamoto.
6. Get Real XOR on Minamoto
Minamoto fees are paid with real XOR. Before submitting write transactions, fund the configured Minamoto account with XOR through an approved mainnet path.
First obtain XOR on SORA 2, then move it into Minamoto. Common mainnet paths are:
- receive XOR from an existing funded SORA 2 account
- use SORA Wallet to hold, receive, and swap supported SORA assets
- use Polkaswap to swap supported assets into XOR on the SORA network
The SORA wiki describes XOR as the SORA network utility token used for transaction fees, and the Polkaswap swap guide explains the normal source-asset to destination-asset swap flow. Check route, slippage, and fees before signing. This documentation does not recommend a specific exchange, bridge, or trade size.
After you have XOR on SORA 2, use the burn-backed Minamoto launch path documented in the SORA Nexus Minamoto Mainnet Launch post:
- Burn XOR on SORA 2 using the published burn interface:
https://bafybeicmlt7f757a64kw2tzmtnmlgpahs7dlmu3nmjssjbbywre6x3nvr4.ipfs.dweb.link/#/burn - Use only burns from SORA 2 block
25,867,650onward for the Minamoto claim flow. - Claim the burned XOR on Minamoto through the SORAFS claim application:
https://minamoto.sora.org/claim - Send a small read-side or balance check first, then use the funded account for fee-paying writes.
You can also receive real Minamoto XOR from an existing funded Minamoto account or an approved operational treasury. Treat Minamoto XOR like production funds: test on Taira first, keep separate keys, and do not assume transactions can be reset.
Do not treat the Taira faucet as a real-XOR source. Testnet XOR cannot pay Minamoto fees and cannot be upgraded into mainnet XOR.
7. Work Inside an Existing Dataspace
Use fully qualified domain names for ledger objects that live inside a dataspace. For example, a project domain in the public dataspace should use:
apps.universalAfter your account has the required permissions, register the domain:
iroha --config ./taira.client.toml ledger domain register --id apps.universalUse the Minamoto config only when the same write is approved for mainnet:
iroha --config ./minamoto.client.toml ledger domain register --id apps.universalAccount aliases use the same dataspace suffix:
alice@apps.universal
alice@universalStrict account fields still use canonical I105 account IDs. Treat aliases as human-readable bindings that resolve to canonical account IDs.
8. Provision a New Dataspace
A new dataspace is an operator and governance change. The public Torii endpoint can route traffic to configured dataspaces, but it will reject unknown dataspace aliases.
Before preparing a change, capture the current live catalog:
curl -fsS https://taira.sora.org/status \
| jq '.teu_lane_commit[] | {lane_id, alias, dataspace_id, dataspace_alias, visibility}'For an operator account, also check the lane manifest posture:
iroha --config ./operator.client.toml app nexus lane-report --summaryDo not promote a new alias unless the lane ID, dataspace ID, validator set, fault tolerance, manifest, routing rules, and operational owner have been reviewed together. A normal user account can register domains inside an existing dataspace; it cannot safely add a new public dataspace.
For a private or organizational dataspace, prepare a catalog change with:
- a unique dataspace alias and numeric
id - a matching lane entry or an existing lane assignment
- the dataspace
fault_tolerance - routing rules for the instructions or account scopes that should land there
- a Space Directory manifest or equivalent rollout evidence, when the dataspace exposes UAID capabilities
- governance approval for validator, compliance, settlement, and monitoring policy
A reviewable config fragment looks like this:
[[nexus.lane_catalog]]
index = 5
alias = "payments"
description = "Payments lane"
dataspace = "payments"
visibility = "public"
metadata = {}
[[nexus.dataspace_catalog]]
alias = "payments"
id = 20
description = "Payments dataspace"
fault_tolerance = 1
[[nexus.routing_policy.rules]]
lane = 5
dataspace = "payments"
[nexus.routing_policy.rules.matcher]
account_prefix = "payments."
description = "Route payments domains to the payments dataspace"Operator acceptance should include these gates:
irohad --sora --config <config.toml> --trace-configpasses on the resolved node configuration- the generated or reviewed manifest is archived with hashes and signatures
- smoke tests pass on Taira before any Minamoto promotion
- the post-change
/statuscatalog shows the intended lane and dataspace iroha app nexus lane-report --summarydoes not report missing required manifests
curl -fsS https://taira.sora.org/status \
| jq '.teu_lane_commit[] | select(.dataspace_alias == "payments")'Promote the same dataspace to Minamoto only after the Taira deployment, smoke tests, monitoring, and governance evidence are complete.