Skip to main content

Android

Add dependency

// settings.gradle.kts
dependencyResolutionManagement {
repositories { google(); mavenCentral(); maven("https://jitpack.io") }
}

// build.gradle.kts (app)
implementation("com.sayists.shyware:shyware-sdk-android:1.0.0")
implementation("com.google.android.play:integrity:1.4.0")
implementation("androidx.security:security-crypto:1.1.0-alpha06")

Requires minSdk 26 (Android 8.0), compileSdk 36 (Android 16). All network calls are suspend functions — call from a coroutine scope or viewModelScope.


Initialization

Every client decodes a ShyConfig manifest and validates it on initialization.

import com.sayists.shyware.*

val config = Json.decodeFromString<ShyConfig>(
context.assets.open("shyconfig.json").bufferedReader().readText()
)

For attestation-gated deployments, resolve Play Integrity signals before any submission:

val integrity = PlayIntegrityProvider(context, cloudProjectNumber = 1234567890L)
val nonce = fetchNonceFromServer()
val (signals, token) = integrity.requestIntegrityToken(nonce)
val trusted = token?.let { verifyIntegrityTokenOnServer(it) } ?: false
client.setRuntimeSignals(if (trusted) signals else RuntimeSignals.untrusted)

val posture = client.effectivePosture()
if (posture.writeOnly) {
// no receipt retained — write-only posture active
}

See Attestation.


Count-match embodiments

VotingClient

val client = VotingClient.from(config)

val result = client.castBallot(pollId = "referendum-2026-1", choice = "yes",
input = IdentityInput.Didit(personId = diditPersonId))
val tally = client.getTally("referendum-2026-1")
val votes = client.getVotes("referendum-2026-1")
val polls = client.getAllPolls()
val receipt = client.loadReceipt(pollId = "referendum-2026-1")
val verified = client.verifyReceipt(receipt!!.ballotNonce, "yes", votes)

WireClient

val client = WireClient.from(config)

val account = client.registerAccount(walletAddress = walletAddress)
val transfer = client.wireSubmission(scopingId = assetId,
senderCommitment = account.accountCommitment,
recipientCommitment = recipientCommitment, amount = 10_00L)
val supply = client.getSupply()
val history = client.getReconcileHistory()
val count = client.getCanonicalCount(transferId = transfer.transferId)
val feed = client.getCanonicalFeed()
client.periodClose(scopingId = scopingId, l1MerkleRoot = l1, l2MerkleRoot = l2)
client.rescind(transferId = transfer.transferId, eligibilityToken = token)

CustodyClient

val client = CustodyClient.from(config)

client.recordIntakeLot(lotId = lotId, policyId = policyId, assetId = assetId,
operatorId = operatorId, warehouseId = warehouseId,
accountCommitment = accountCommitment, skuClassId = "XAU-9999",
quantity = 100.0, mintedAmount = 100L,
videoSessionRef = sessionRef, evidenceRefs = refs)
client.mintSilo(assetId = assetId, accountCommitment = accountCommitment, amount = 100L)
client.transferSilo(assetId = assetId, senderCommitment = senderCommitment,
recipientCommitment = recipientCommitment, amount = 50L)
val redemption = client.requestLotRedemption(assetId = assetId,
accountCommitment = accountCommitment, warehouseId = warehouseId,
skuClassId = "XAU-9999", siloAmount = 50, requestedQuantity = 50)
val balance = client.getSiloBalance(assetId = assetId, accountCommitment = accountCommitment)

ContractsClient

val client = ContractsClient.from(config)

val contract = client.registerContract(assetId = assetId,
parties = listOf(partyCommitment, counterpartyCommitment),
contractType = "rbf", termsRef = termsRef)
client.activateContract(contractId = contract.contractId)
val execution = client.executeContract(contractId = contract.contractId,
partyCommitment = partyCommitment, counterpartyCommitment = counterpartyCommitment,
sourceRef = sourceRef, amount = 500_00L)
val state = client.getContractState(contractId = contract.contractId)

SharesClient

val client = SharesClient.from(config)

val proposal = client.createProposal(proposalClass = "parameter_change",
question = "Increase fee to 0.5%?", options = listOf("yes", "no"),
startTime = startTime, endTime = endTime)
client.submitWeightedBallot(proposalId = proposal.proposalId,
choice = "yes", accountCommitment = accountCommitment)
val tally = client.getProposalTally(proposalId = proposal.proposalId)
val queue = client.getActionQueue()
val snapshot = client.getMembershipSnapshot()
client.dispatchAction(actionId = actionId, adapter = "shywire",
adapterPayload = mapOf("amount" to 1000, "recipientCommitment" to recipientCommitment))

BetsClient

// Single ShyConfig with wire + bets blocks — BetsClient embeds WireClient
val client = BetsClient.from(config)

client.registerSettlementAccount(walletAddress = walletAddress)
client.createFundingIntent(amount = 500_00L, destinationNetwork = "ethereum", destinationAddress = addr)
val event = client.createEvent(marketId = marketId, title = "Match outcome",
outcomes = listOf("home", "away", "draw"), closesAt = closesAt)
val order = client.placeOrder(eventId = event.eventId, side = "back",
outcome = "home", stake = 500L, odds = 2.1, accountCommitment = accountCommitment)
val settlement = client.settleEvent(eventId = event.eventId, winningOutcome = "home")
val wire = client.getSettlementClient() // access internal WireClient

LotsClient

// Single ShyConfig with custody + wire + lots blocks — LotsClient pairs CustodyClient + WireClient
val client = LotsClient.from(config)

val account = client.registerBidderAccount(walletAddress = walletAddress)
client.transferBidBond(senderCommitment = account.accountCommitment,
recipientCommitment = escrowCommitment, amount = 1000L)
client.settleAwardTransfer(senderCommitment = escrowCommitment,
recipientCommitment = winnerCommitment, amount = 5000L)
client.requestLotRedemption(assetId = assetId,
accountCommitment = account.accountCommitment, warehouseId = warehouseId,
skuClassId = "warehouse_lot", siloAmount = 1, requestedQuantity = 1)

Sealer-governed embodiments

Implement SealerKeyProvider to supply the AES-256-GCM sealing key:

class MyKeyProvider : SealerKeyProvider {
override fun deriveSealerKey(config: ShyConfig, input: IdentityInput): ByteArray {
// derive from operator secret + identity commitment
}
}
val provider = MyKeyProvider()

StoreClient

val client = StoreClient.from(config, provider)

val stored = client.storeSubmission(scopingId = bucketId,
plaintext = jsonString, category = "health_record")
val revealed = client.revealAndDecryptStore(scopingId = bucketId,
submissionId = stored.submissionId)
val buckets = client.listBuckets(scopingId = bucketId)
client.deleteStore(scopingId = bucketId, submissionId = stored.submissionId)

ChatClient

val client = ChatClient.from(config)

val mailbox = client.createMailbox(label = "Inbox", address = address)
val dispatch = client.queueDispatch(mailboxId = mailbox.mailboxId,
recipientAddress = recipientAddress, subject = "Update",
body = body, contentClass = "mail_text")
val inbox = client.getInbox(mailboxId = mailbox.mailboxId)
client.closeDispatch(dispatchId = dispatch.dispatchId)

RestClient

// Single ShyConfig with store + messaging blocks — RestClient pairs StoreClient + ChatClient
val client = RestClient.from(config, provider)

client.storeSubmission(scopingId = bucketId, plaintext = data, category = "review_submission")
client.queueDispatch(mailboxId = mailboxId, recipientAddress = addr,
subject = "Received", body = body, contentClass = "review_packet")

val storeClient = client.getStoreClient()
val chatClient = client.getChatClient()

StreamClient

// Single ShyConfig with store + stream blocks — StreamClient extends StoreClient
val client = StreamClient.from(config, provider)

client.createBucket(scopingId = "session-42",
allowedCategories = listOf("stream_event", "stream_clip"))
client.sealLiveSegment(bucketID = "session-42",
streamID = streamID, sequence = 0, segment = segmentBytes)

val queue = client.createLiveQueue(bucketID = "session-42",
streamID = streamID, minBatchSize = 3, flushIntervalMs = 4000)
queue.enqueue(chunk)

BrowserClient

val client = BrowserClient.from(config)

client.storeSealedBrowserData(data = eventData, category = "browser_session")
client.submitList2IdentityAttribute(attribute = ipAddress, attributeType = "ip_address")

Write-only posture

All submission clients check effectivePosture() before persisting any receipt. When write-only, EncryptedSharedPreferences write is suppressed.

val posture = client.effectivePosture()
// posture.writeOnly → true when coercion_resistant or any fallback triggered
// posture.recoverable → true when device + network signals both pass
if (posture.writeOnly) showWriteOnlyUI(posture.fallbackReasons)

Receipt storage (recoverable posture only) uses Android Keystore-backed AES-256-GCM. Cross-device recovery through Didit biometric re-derivation — no seed phrase required. See Attestation.