Skip to main content

contractsClient

Initialize with initializeFromShyConfig — see the Web SDK overview for the common setup pattern.

Return basis modes

return_basisDescription
project_profitRemittances are a share of reported project profit
customer_revenueRemittances are matched to incoming customer revenue
eligible_incomeRemittances 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_modeDescription
single_lenderOne capital provider per contract
staggered_waitlistInvestors join sequentially as tranches are filled
shared_financingMultiple 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.