@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
class LiquidCredentialProviderService: CredentialProviderService() {
private val credentialRepository = CredentialRepository()
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.IO + job)
const val TAG = "LiquidCredentialProviderService"
const val GET_PASSKEY_INTENT = 1
const val CREATE_PASSKEY_INTENT = 2
const val GET_PASSKEY_ACTION = "foundation.algorand.demo.GET_PASSKEY"
const val CREATE_PASSKEY_ACTION = "foundation.algorand.demo.CREATE_PASSKEY"
* Handle Create Credential Requests
override fun onBeginCreateCredentialRequest(
request: BeginCreateCredentialRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException>
val response: BeginCreateCredentialResponse? = processCreateCredentialRequest(request)
callback.onResult(response)
callback.onError(CreateCredentialUnknownException())
* Process incoming Create Credential Requests
private fun processCreateCredentialRequest(request: BeginCreateCredentialRequest): BeginCreateCredentialResponse? {
is BeginCreatePublicKeyCredentialRequest -> {
return handleCreatePasskeyQuery(request)
* Create a new PassKey Entry
* This returns an Entry list for the user to interact with.
* A PendingIntent must be configured to receive the data from the WebAuthn client
private fun handleCreatePasskeyQuery(
request: BeginCreatePublicKeyCredentialRequest
): BeginCreateCredentialResponse {
Log.d(TAG, request.requestJson)
val createEntries: MutableList<CreateEntry> = mutableListOf()
val name = JSONObject(request.requestJson).getJSONObject("user").get("name").toString()
createEntries.add( CreateEntry(
createNewPendingIntent(CREATE_PASSKEY_ACTION, CREATE_PASSKEY_INTENT, null)
return BeginCreateCredentialResponse(createEntries)
* Handle Get Credential Requests
override fun onBeginGetCredentialRequest(
request: BeginGetCredentialRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<BeginGetCredentialResponse, GetCredentialException>,
callback.onResult(processGetCredentialRequest(request))
} catch (e: GetCredentialException) {
callback.onError(GetCredentialUnknownException())
* Fake a list of available PublicKeyCredential Entries
private fun processGetCredentialRequest(request: BeginGetCredentialRequest): BeginGetCredentialResponse{
Log.v(TAG, "processing GetCredentialRequest")
val deferredCredentials: Deferred<List<Credential>> = scope.async {
credentialRepository.getDatabase(this@LiquidCredentialProviderService).credentialDao().getAllRegular()
val credentials = runBlocking {
deferredCredentials.await()
return BeginGetCredentialResponse(credentials.map {
data.putString("credentialId", it.credentialId)
data.putString("userHandle", it.userHandle)
PublicKeyCredentialEntry.Builder(
this@LiquidCredentialProviderService,
createNewPendingIntent(GET_PASSKEY_ACTION, GET_PASSKEY_INTENT, data),
// TODO: filter the request for PublicKeyCredentialOptions
request.beginGetCredentialOptions[0] as BeginGetPublicKeyCredentialOption
.setIcon(Icon.createWithResource(this@LiquidCredentialProviderService, R.mipmap.ic_launcher))
override fun onClearCredentialStateRequest(
request: ProviderClearCredentialStateRequest,
cancellationSignal: CancellationSignal,
callback: OutcomeReceiver<Void?, ClearCredentialException>
Log.d(TAG, "onClearCredentialStateRequest")
TODO("Not yet implemented")
private fun createNewPendingIntent(action: String, requestCode: Int, extra: Bundle?): PendingIntent{
val intent = Intent(action).setPackage("foundation.algorand.demo")
intent.putExtra("CREDENTIAL_DATA", extra)
return PendingIntent.getActivity(
applicationContext, requestCode,
intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
override fun onDestroy() {