Shield6 for exchanges and service providers
Shield6 usage guide · Local wallet setup · Interactive console guide
Provider source · Backend SDK · Linux build artifacts
This source includes a locally signed provider kit for chain 8979. Build it from this revision; the older v1.21.83 binary does not contain these new commands. The kit wraps existing consensus transactions; no network fork is required. The signer is validated on Linux and needs persistent local storage with POSIX file permissions. Use one signing host per account; this kit does not implement distributed leader election or cross-host nonce coordination.
It includes a Go API (github.com/ethereum/go-ethereum/shield6provider), a non-interactive CLI (shield6-provider, also gtkm wallet shield6), an optional authenticated loopback JSON-RPC service, and a dependency-free Node.js client in sdk/shield6-provider. No npm package has been published.
The Go fork retains the upstream github.com/ethereum/go-ethereum module path. External Go services must use a module replace directive pointing to this fork or a local checkout; upstream go-ethereum does not include this provider package.
1. Build and install public parameters
make shield6-provider gtkm
install -d -m 700 /srv/tkm-provider /srv/tkm-provider/state
./build/bin/shield6-provider parameters \
--parameters /srv/tkm-provider/parameters
The installer uses the ten production file hashes embedded in the binary. It checks sizes and SHA-256 hashes before installing, refuses mismatched existing files, and reuses verified files. It downloads about 648 MB of uncompressed public artifacts once. Neither the RPC endpoint nor a request can select setup keys. No circuit compilation or trusted setup runs while sending a payment.
The production parameters use a single-operator Groth16 setup, without an independent multiparty ceremony or independent audit. This kit does not change those trust assumptions. Account signatures use ML-DSA-87; this does not make all Shield6 proof primitives post-quantum.
2. Create or restore the provider's accounts
Use gtkm account new to create a local encrypted ML-DSA account with its private name/country stamp. For automation, use --stamp-file with a private JSON file containing name and a supported full country name (for example Egypt), and --password with a private password file. Creation is offline. Back up the original encrypted keyfile, including its immutable stamp, before registration. Restoration must retain that stamp, not create a new stamp for the same account.
Keep keyfiles and password files mode 0600 and the state directory mode 0700. Use absolute paths in service configuration. Do not put passwords in command arguments or send seeds/passwords to network RPC. This implementation uses local software ML-DSA keyfiles; it does not claim support for an external HSM signer.
Run a fully synced mainnet node with the production Shield6 fork configuration. IPC is recommended; a trusted network RPC can also be used, including https://wallet.tkmchain.site/rpc when healthy and exposing the required methods. Onboarding checks chain ID, production parameter pins, eth_syncing and head freshness. A syncing or stale node is rejected with a retryable error.
Fund the account's public TKM balance for registration gas. A private note balance does not pay these account transaction fees. The initial preflight checks both remaining gas budgets before submitting the first stamp. A maximum fee is a ceiling, not a quoted fee; actual inclusion costs depend on the network.
3. Prepare an account with one command
./build/bin/shield6-provider prepare \
--rpc /srv/tkm-node/gtkm.ipc \
--keyfile /srv/tkm-provider/keys/deposits.json \
--password-file /srv/tkm-provider/secrets/deposits.password \
--state-dir /srv/tkm-provider/state \
--parameters /srv/tkm-provider/parameters \
--max-fee-wei 1000000000000000000 \
--wait --timeout 30m
The example explicitly authorizes at most 1 TKM of gas budget per transaction; choose the ceiling according to your policy. Stamping and recipient registration are separate transactions and can incur two fees. The workflow skips matching existing registry entries, waits for 12 canonical confirmations for each stage, and returns ready: true and a tkmshield6r. payment code. Run the same command without --wait for one state-machine step. Use status with the same paths for checks that never sign or broadcast. JSON output has result and/or error; an error exits nonzero and can still include the last pending transaction hash.
The equivalent integrated command is gtkm wallet shield6 prepare [flags]. A preexisting recipient binding without a locally tracked transaction is conservatively observed for 12 blocks from first detection. An already-ready account returns immediately. Proof construction time depends on CPU/memory; confirmation time depends on new canonical blocks, not a fixed wall-clock delay.
The state directory is part of the operational backup. Transactions are signed, saved with fsync and atomic replacement, and only then broadcast. Repeating a request after a timeout checks receipts/mempool first and reuses identical signed bytes if rebroadcast is needed. Every process controlling an account must share this state directory, and external signing systems must respect its nonce reservations. Do not give the interactive wallet or another signer concurrent ownership of the same account nonce.
4. Run a private provider API
Create /srv/tkm-provider/provider.json, mode 0600:
{
"rpc": "/srv/tkm-node/gtkm.ipc",
"chainId": 8979,
"stateDir": "/srv/tkm-provider/state",
"parametersDir": "/srv/tkm-provider/parameters",
"confirmations": 12,
"maxFeeWei": "1000000000000000000",
"paymentsEnabled": false,
"accounts": {
"deposits": {
"keyfile": "/srv/tkm-provider/keys/deposits.json",
"passwordFile": "/srv/tkm-provider/secrets/deposits.password"
}
}
}
Generate an authentication token once, keep it mode 0600, then start the service:
(umask 077; openssl rand -hex 32 > /srv/tkm-provider/secrets/api.token)
./build/bin/shield6-provider serve \
--config /srv/tkm-provider/provider.json \
--token-file /srv/tkm-provider/secrets/api.token \
--listen 127.0.0.1:8796
Manage this process with your usual service manager. Use persistent storage for state and keep secrets outside public web roots. Restarting the service retains the journal and idempotency records. Do not regenerate its token on every start. Only an explicit loopback IP is accepted. Browser origins are rejected; the API is for the provider backend, not a browser or a public reverse proxy. It permits configured account aliases only, not request-selected key/password files. Keep the bearer token inside the backend. Possession authorizes the configured account operations, including spending if enabled.
To enable transfers, deliberately set paymentsEnabled: true and add a positive integer maxTransferWei ceiling, for example "100000000000000000000" for at most 100 TKM per payment. This is a per-operation ceiling, not a daily allowance. Application-level withdrawal authorization and rate limits remain the provider's responsibility. Gas ceilings apply separately.
The API accepts single JSON-RPC requests with an ID; batches and notifications are not supported. All requests use POST /rpc, Content-Type: application/json and Authorization: Bearer <token>. Parameters are objects:
| Method | Parameters | Effect |
|---|---|---|
provider_prepare | account | Advance onboarding by one pending stage |
provider_status | account | Check readiness; no signing or broadcast |
provider_scan | account | Decrypt full canonical output history locally |
provider_stampCode | account | Export public stamp code for sponsorship |
provider_sponsorOffer | account, code | Reserve a stamp fee offer |
provider_sponsorAuthorize | account, code | Beneficiary authorizes the exact offer |
provider_sponsorSubmit | account, code | Sign, journal and submit/resume sponsored stamp |
provider_transaction | account, operationId | Read-only receipt check for a saved payment ID |
provider_transfer | account, transfer | Deposit, private send or public withdrawal |
transfer contains id, kind, amountWei and, except for deposits, recipient. An ID contains 1–128 letters, digits, _, - or .. Store it permanently in the application database before sending the request. Retrying the same ID with changed fields is rejected. Keep the same receiving code string on retries; refreshing it belongs to a newly authorized request after reconciliation. Use provider_transaction (SDK transaction(account, id), CLI transaction --id ID) for a read-only receipt check, including after payments have been disabled or their amount limit lowered.
Amounts are canonical integer strings in wei, never JavaScript floating point.
5. Backend SDK: setup, receive, confirm and withdraw
Use Node.js 20.3+ and import the checked-out client:
import {readFile} from 'node:fs/promises';
import {Shield6ProviderClient} from './sdk/shield6-provider/index.mjs';
const token = (await readFile('/srv/tkm-provider/secrets/api.token', 'utf8')).trim();
const provider = new Shield6ProviderClient({token});
const account = 'deposits';
const ready = await provider.waitUntilReady(account, {timeoutMs: 1_800_000});
console.log(ready.paymentCode); // Share privately with the payer.
const snapshot = await provider.scan(account);
for (const output of snapshot.deposits) {
// Persist/reconcile the complete snapshot at snapshot.headHash.
// Credit only confirmed EXTERNAL deposits under your accounting rules.
console.log(output.id, output.amountWei, output.confirmed, output.spent);
}
// Run only after your withdrawal authorization and funds checks succeed.
// Persist this request before calling: its ID is never replaced on timeout.
const withdrawal = {
id: 'withdrawal-2026-000001',
kind: 'withdraw',
amountWei: '1000000000000000000',
recipient: '0xREPLACE_WITH_A_STAMPED_PUBLIC_ADDRESS'
};
const sent = await provider.waitForTransfer(account, withdrawal);
console.log(sent.transaction.transactionHash);
kind: "send" uses the recipient's full tkmshield6r. code for a private transfer. kind: "deposit" shields public funds into this account and omits recipient. kind: "withdraw" releases private value to a stamped public address; this is a public boundary. Only notes with at least the configured confirmations are selected. A single transaction permits at most 16 inputs. Insufficient private funds and input-limit failures never trigger an automatic deposit, consolidation or another debit. Transfer methods poll the same durable ID and return once the receipt has 12 canonical confirmations.
The CLI equivalent is transfer with the same connection/account/state flags as prepare, plus --id, --kind, --amount-wei and optional --recipient. Repeat the exact command to resume and check confirmation. Transfer --wait is not used; the SDK supplies polling, or the caller can poll the CLI.
Deposit accounting and reorganizations
Map each configured receiving account to a customer in your private database. This kit does not introduce deterministic subaddresses, payment memos or a network-visible customer directory. For distinct customer attribution, use separate properly backed-up and registered accounts, or another independently validated attribution scheme. A shared receiving code alone cannot identify which customer should be credited.
Scan results contain all owned canonical outputs, including spent outputs, change and self-funding. A spent output remains in receipt history; it is not a reason to reverse a deposit credit. Exclude the provider's own funding/change transaction hashes from customer credits. confirmed describes confirmation depth only; it does not classify an external customer deposit.
Use (chainId, account, output.id) as the receipt key. The ID is transaction hash plus transaction output position, so accumulator reindexing does not duplicate a credit. Retain block hashes and reconcile the full successful scan snapshot against the previous one. Removed or moved receipts need reorg reconciliation, including already-credited records; do not merely append every scan. Persist credits/debits and snapshot changes atomically in your accounting database. A scan rejects a changing chain head instead of returning a partial snapshot. The first implementation scans the public history on each call; benchmark this on your expected history size before choosing the polling frequency.
6. Sponsor stamping for an unfunded user
stamp-code (a tkmshield3. identity code used only for this handshake).
local key and returns the resulting code to the sponsor.
that same command to resume/check confirmations.
stamp is confirmed and its public gas balance is funded.
- The beneficiary creates and backs up its stamped ML-DSA account, then exports
- The funded, stamped sponsor runs
sponsor-offer --code-file beneficiary.txt. - The beneficiary runs
sponsor-authorize --code-file offer.txtwith its own - The sponsor runs
sponsor-submit --code-file authorization.txtand repeats - The beneficiary runs
preparefor Shield6 recipient registration once its
Each CLI invocation uses its own account's normal RPC/key/password/state flags. The offer and authorization codes are public signed exchange data, not seeds; share them privately to avoid leaking account relationships. Do not expose the local provider API to the beneficiary: exchange these codes through your normal application backend. Offers reserve the sponsor's nonce in the local journal until confirmation or unsigned-offer expiry; avoid parallel fee-payer activity. Stamp sponsorship does not sponsor Shield6 recipient registration. Fully gasless recipient registration is not advertised or implemented by this kit.
Errors and recovery
| Code | Required action |
|---|---|
NODE_SYNCING, STALE_HEAD, HEAD_CHANGED, SCAN_REORG, BUSY | Wait, then repeat; discard incomplete scan results |
BROADCAST_UNCERTAIN, SDK OUTCOME_UNCERTAIN | Keep the state directory and payment ID; check/resume the same operation |
GAS_FUNDS_REQUIRED | Deliberately fund public gas balance; retry the same onboarding |
PARAMETERS_REQUIRED | Run the pinned installer; inspect mismatches rather than replacing pins |
FEE_LIMIT_REQUIRED, FEE_LIMIT_EXCEEDED | Review and explicitly configure an appropriate fee ceiling |
STAMP_MISMATCH, RECIPIENT_MISMATCH | Restore the original keyfile; registrations are immutable |
ACCOUNT_BUSY, SPONSOR_PENDING, ONBOARDING_PENDING | Finish the operation that reserved the account nonce |
IDEMPOTENCY_CONFLICT | Reuse the original request; never change fields under an existing ID |
TX_REVERTED, TX_EXPIRED, NONCE_CONFLICT | Stop automatic submissions and reconcile canonical receipt/nonce history before an operator authorizes replacement |
Do not delete a pending journal to bypass an error. The kit intentionally does not automatically replace transactions with higher fees or fresh nonces. Keep original records for recovery and an audit trail. A terminal-error replacement needs an operator's reviewed recovery procedure. Test your full flow on controlled accounts, including process restarts, delayed receipts and reorg accounting, before accepting customer funds. Do not benchmark by creating repeated real payments against an uncertain previous result.
Validation
go test -p 1 -tags shield3 ./shield6provider ./internal/shield6providercli
node --test sdk/shield6-provider/index.test.mjs
Tests exercise readiness and reorg depth, ambiguous broadcasts, persistent payment IDs, account nonce reservations, authenticated parameter installation, spent receipt retention, API authentication/account boundaries and SDK retries. They do not constitute an independent cryptographic audit or a mainnet payment benchmark.