Skip to main content

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.