custodyClient
Initialize with
initializeFromShyConfig— see the Web SDK overview for the common setup pattern.
Custody governance upgrade
A shycustody-v1 deployment can add anonymous holder governance by promoting to
shyshares-v1 and adding a governance block to the shyconfig. The custody block
is unchanged — all existing flows (lot intake, redemption, transfer, demurrage) continue
without modification. Governance is purely additive: a Governance tab appears in the
console when the board creates a poll.
{
"contract_version": "shyshares-v1",
"custody": { ... },
"governance": {
"poll_create_authority": "operator_or_board",
"poll_create_keys": ["<operator-pubkey-hex>"],
"membership_sources": ["token_balance"],
"proposal_classes": ["custody_policy", "warehouse_operator", "accepted_sku_class"],
"eligibility": { "asset_id": "silo", "min_balance": 1 },
"vote_weight": "token_quantity"
}
}
See sharesClient for the governance client API and
shyshares deployments for the custody consortium model.
Policy and operators
await client.getCurrentPolicy();
// → ConsortiumPolicy { assetId, operators, fungibilityRules, redemptionRouting }
await client.listPolicies();
await client.getPolicy(policyId);
await client.listOperators();
// → WarehouseOperator[] (identified by operator commitment hash)
await client.getOperator(operatorId);
The consortium policy is stored on-chain as a canonical record. Accepted SKU whitelist and fungibility rules are read from the policy, not the shyconfig.
Asset and supply
await client.getAsset(assetId);
// → Asset { id, unitOfMeasure, maxSupply, demurragePolicy }
await client.getSupply(assetId);
// → { minted, burned, net } — publicly auditable conservation
await client.getBalance(assetId, accountCommitment);
// → { balance } — requires protected linkage store access
getSupply is public — it returns aggregate mint count minus burn count per asset, verifiable by any third party without operator cooperation. getBalance returns the specific holder's balance and requires the caller to hold a valid account commitment.
SKUs and lots
await client.listSkuClasses();
// → SkuClass[] (accepted grade bands per asset)
await client.getSkuClass(skuClassId);
await client.listLots();
// → IntakeLot[]
await client.getLot(lotId);
// → IntakeLot { id, operatorId, assetId, quantity, evidenceRefs, height }
Redemptions
await client.listRedemptions();
// → RedemptionRequest[]
await client.getRedemption(requestId);
await client.listSettlements();
// → RedemptionSettlement[]
await client.getSettlement(settlementId);
Demurrage
await client.listDemurrage();
// → DemurrageAssessment[]
await client.getDemurrageAssessment(assessmentId);
Demurrage burns tokens from the supply according to the demurrage_policy in the shyconfig (policy_burn). The burn is applied as a TxType 16 transaction. The effect is visible in getSupply without revealing which holder was affected.
Build and submit (operator)
All build methods return { txJson, ...metadata }. Submit with client.submit(txJson) or pass to dispatch.
Asset registration
// Register a new asset class (operator only)
await operatorClient.buildRegisterAsset({
assetId: "vault-gold-1",
unitOfMeasure: "troy_oz",
maxSupply: 100000
});
// → TxType 1
Account registration
// Register a participant account commitment (operator only)
await operatorClient.buildRegisterAccount({
accountCommitment: H("account:wallet:0xabc...")
});
// → TxType 5
Mint (deposit)
// Issue tokens for a physical deposit (operator only)
await operatorClient.buildMintSilo({
assetId: "vault-gold-1",
amount: 10.5,
depositorCommitment: accountCommitment,
lotId: "intake-lot-001"
});
// → TxType 2
// List 1: (assetId, amount, "mint") — no depositor identity
// List 2: depositorCommitment — no asset amount
Transfer
// Transfer silo units (participant — routed through shywire rail)
const { txJson, transferId, nullifier } = await client.buildTransferSilo({
assetId: "vault-gold-1",
amount: 2.0,
senderCommitment: myCommitment,
recipientCommitment: recipientCommitment
});
// → TxType 4
// transferId = H(randomNonce)
// nullifier = H(senderCommit : assetId : nonce)
Redemption request (participant)
const { txJson, requestId } = await client.buildRequestRedemption({
assetId: "vault-gold-1",
amount: 5.0,
redeemerCommitment: myCommitment,
preferredWarehouse: "warehouse-nyc-1"
});
// → TxType 14
Redemption settlement (operator)
await operatorClient.buildSettleRedemption({
requestId,
shippingRef: "SHIP-12345",
operatorReceiptRef: "RCPT-67890"
});
// → TxType 15
Intake lot record (operator)
await operatorClient.buildRecordIntakeLot({
assetId: "vault-gold-1",
operatorId: operatorCommitment,
quantity: 10.5,
skuClassId: "XAU-9999",
evidenceRefs: {
camera_session_ref: "session-abc",
operator_receipt_ref: "rcpt-def",
kyc_attestation_ref: "kyc-xyz"
}
});
// → TxType 13
Evidence requirements
The custody shyconfig declares which evidence types are required for an intake lot to be accepted:
"evidence_requirements": ["camera_session_ref", "operator_receipt_ref", "kyc_attestation_ref"]
buildRecordIntakeLot validates that all declared evidence types are provided before building the transaction.