contractsClient
Initialize with
initializeFromShyConfig— see the Web SDK overview for the common setup pattern.
Return basis modes
return_basis | Description |
|---|---|
project_profit | Remittances are a share of reported project profit |
customer_revenue | Remittances are matched to incoming customer revenue |
eligible_income | Remittances are matched to a defined income category |
The return basis determines how buildFinancingRemittance validates the matchedAmount field and which remittance_source_mode is valid.
Funding modes
funding_mode | Description |
|---|---|
single_lender | One capital provider per contract |
staggered_waitlist | Investors join sequentially as tranches are filled |
shared_financing | Multiple investors share a single contract with defined allocations |
Contract lifecycle
1. Register contract
const { txJson, contractId, contractHash } = await client.buildRegisterFinancingContract({
borrowerCommitment,
offchainTerms: {
financeType: 'revenue_share',
totalAmount: 500000,
revShareBps: 1500, // 15%
termMonths: 24,
goalAmount: 400000 // only relevant if goal_gated: true
},
tranches: [
{
classId: 'senior',
investorCommitment: investorCommitment1,
allocationBps: 7000, // 70%
seniority: 1,
status: 'active'
},
{
classId: 'junior',
investorCommitment: investorCommitment2,
allocationBps: 3000,
seniority: 2,
status: 'active'
}
]
})
// → TxType 7
// contractHash = H(offchainTerms + financeType + ...)
// contractId = H(contractHash : timestamp : randomHex(16))
// List 1: (assetId, disbursementAmount, contractHash) — no counterparty identity
// List 2: borrowerCommitment — no contract terms
contractHash is the on-chain commitment to the off-chain terms. An auditor with the off-chain terms can verify that contractHash matches the on-chain record while observers learn only that a financing event of the committed amount occurred.
2. Activate contract (goal-gated)
Required when goal_gated: true. Signals that the fundraising goal has been met and disbursement should proceed.
await client.buildActivateFinancingContract({
contractId,
goalEvidence: 'evidence-doc-ref-123'
})
// → TxType 9
// goalEvidenceHash = H(goalEvidence)
3. Submit remittance
const { txJson, transferId, nullifier } = await client.buildFinancingRemittance({
contractId,
payerCommitment,
matchedAmount: 12500,
sourceRef: 'invoice-2024-03',
incomeCategory: 'customer_revenue'
})
// → TxType 8
// transferId = H(transferNonce)
// nullifier = H(payerCommit : contractId : sourceRef)
// List 1: (contractHash, remittanceAmount) — no payer identity
// List 2: payerCommitment — no contract terms
Observers see aggregate remittance volume per contract identifier but cannot identify the paying entity. The KMS-backed tally attestation covers total disbursed and total remitted per contract.
ZK extension
The ZK nullifier applies to the remittance flow:
// With ZK prover initialized:
const nullifier = zkClient.computeNullifier(walletSecret, contractId)
// nullifier = MiMC(walletSecret, contractId)
// Prevents a single wallet from submitting duplicate remittance records
// for the same contract without revealing the wallet address on-chain
Secondary-market stake transfers
Contract stake positions can be transferred between investors via the shywire rail:
// Transfer stake position (routed through shywire TxTypeTransfer=4)
const wireClient = await initializeFromShyConfig(wireShyconfig)
await wireClient.buildTransfer({
assetId: contractId,
senderCommitment,
recipientCommitment,
amount: allocationBps
})
Remittances (operator → lender disbursements) are out-of-band and are not routed through shywire.