Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces Solana Attestation Service (SAS) support end-to-end (instruction builders, API requests, and public SDK entrypoints), while also refactoring Vault initialization/auth flows and updating Android/Gradle build configuration plus the sample app UX.
Changes:
- Add SAS program instruction builders + Retrofit endpoints + SDK methods for createSchema/attest/revoke.
- Improve Vault/VaultStorage resilience (stale keyset purge) and change init flow to unlock immediately and cache public key.
- Update build tooling (AGP/Gradle/compileSdk) and refactor the sample app to view-based examples with new screens.
Reviewed changes
Copilot reviewed 54 out of 58 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| vault/src/main/java/com/altude/vault/storage/VaultStorage.kt | Adds stale keyset detection/purging and master key rebuild logic; changes keystore init/clear semantics. |
| vault/src/main/java/com/altude/vault/model/VaultStorageCorruptedException.kt | New exception for stale/mismatched encrypted-file keyset scenarios. |
| vault/src/main/java/com/altude/vault/model/VaultSigner.kt | Allows injecting an initial cached public key to avoid first-derivation overhead. |
| vault/src/main/java/com/altude/vault/model/VaultException.kt | Adds new error code VAULT-0305 for stale keyset. |
| vault/src/main/java/com/altude/vault/crypto/BiometricHandler.kt | Updates biometric availability strategy and attempts to handle API 28 credential limitations. |
| vault/build.gradle.kts | Bumps compileSdk to 36 for the vault module. |
| smart-account/build.gradle.kts | Bumps compileSdk to 36. |
| nft/build.gradle.kts | Bumps compileSdk to 36. |
| gasstation/build.gradle.kts | Bumps compileSdk to 36. |
| core/build.gradle.kts | Bumps compileSdk to 36. |
| settings.gradle.kts | Removes foojay toolchain resolver convention plugin block. |
| gradle/wrapper/gradle-wrapper.properties | Changes Gradle wrapper distribution (and adds sha256 sum). |
| gradle/libs.versions.toml | Bumps AGP version to 8.10.0. |
| gradle/gradle-daemon-jvm.properties | Removes daemon JVM toolchain properties file. |
| gradle.properties | Adds multiple Android/AGP-related feature flags and defaults. |
| build.gradle.kts | Removes unused buildToolsVersion extra and whitespace changes. |
| gasstation/src/main/java/com/altude/gasstation/data/SwapOption.kt | Makes account optional with a default empty string. |
| gasstation/src/main/java/com/altude/gasstation/data/GetHistoryOption.kt | Adds defaults for account/limit/offset/walletAddress. |
| gasstation/src/main/java/com/altude/gasstation/data/AttestationResponse.kt | New response model for attestation operations. |
| gasstation/src/main/java/com/altude/gasstation/data/AttestationOption.kt | New option models for attesting, revoking, and schema creation. |
| core/src/main/java/com/altude/core/data/AttestationRequest.kt | New request bodies for SAS API endpoints. |
| core/src/main/java/com/altude/core/api/TransactionService.kt | Adds Retrofit endpoints for SAS createSchema/attest/revoke. |
| core/src/main/java/com/altude/core/Programs/AttestationProgram.kt | New SAS instruction builders + PDA derivations + binary encoding. |
| gasstation/src/main/java/com/altude/gasstation/AltudeGasStation.kt | Initializes storage earlier; unlocks vault during init to prompt once and capture public key. |
| gasstation/src/main/java/com/altude/gasstation/Altude.kt | Adds FragmentActivity-based init overload + per-call signer overrides + SAS methods. |
| core/src/main/java/com/altude/core/service/StorageService.kt | Refactors keystore handling and recovery flow; changes error behavior to return null on decrypt failure. |
| app/src/main/res/values/themes.xml | Switches theme parent to MaterialComponents and keeps color item mapping. |
| app/src/main/res/layout/activity_vault_example.xml | Adds wallet address display + copy/reveal buttons. |
| app/src/main/res/layout/activity_signer_examples.xml | New layout for signer comparison example screen. |
| app/src/main/res/layout/activity_main.xml | New main menu layout for selecting sample screens. |
| app/src/main/res/layout/activity_error_handling_example.xml | Refactors error handling demo layout and adds clear button/progress bar. |
| app/src/main/java/com/altude/android/VaultExampleActivity.kt | Reworks example to use Altude.setApiKey(activity, ...), add reveal/copy wallet flows, and unified error handling. |
| app/src/main/java/com/altude/android/SignerExamplesActivity.kt | New activity demonstrating default vault vs custom signer usage. |
| app/src/main/java/com/altude/android/MainActivity.kt | Replaces Compose launcher with view-based menu launcher. |
| app/src/main/java/com/altude/android/ErrorHandlingExampleActivity.kt | Reworks to trigger “real” SDK error paths and adds vault clearing flow. |
| app/src/main/AndroidManifest.xml | Changes launcher to MainActivity; registers new sample activities. |
| app/build.gradle.kts | Removes Compose, uses Material components, adjusts packaging flags. |
| app/src/main/java/com/altude/android/ui/theme/Type.kt | Deletes Compose typography file (Compose removed). |
| app/src/main/java/com/altude/android/ui/theme/Theme.kt | Deletes Compose theme file (Compose removed). |
| app/src/main/java/com/altude/android/ui/theme/Color.kt | Deletes Compose color palette file (Compose removed). |
| VERIFICATION_COMPLETE.md | Deletes verification doc. |
| READY_TO_BUILD.md | Deletes build readiness doc. |
| QUICK_FIX_REFERENCE.md | Deletes quick fix reference doc. |
| QUICK_FIX_CHECKLIST.md | Deletes quick fix checklist doc. |
| PHASE_6_COMPLETION_SUMMARY.md | Deletes phase completion summary doc. |
| INDEX.md | Deletes documentation index doc. |
| IMPLEMENTATION_GUIDE.md | Deletes implementation guide doc. |
| GRADLE_FIX_FINAL.md | Deletes gradle fix doc. |
| GET_STARTED.md | Deletes getting started doc. |
| FIX_SUMMARY.md | Deletes fix summary doc. |
| FIXES_APPLIED_SUMMARY.md | Deletes fixes applied summary doc. |
| DETAILED_CHANGES.md | Deletes detailed changes doc. |
| 16KB_PAGE_SIZE_FIX_SUMMARY.md | Deletes 16KB fix summary doc. |
| 16KB_ALIGNMENT_FIX_FINAL.md | Deletes 16KB alignment final doc. |
| .idea/gradle.xml | Sets IDE Gradle JVM to jbr-21 and enables parallel model fetch. |
| .idea/deploymentTargetSelector.xml | Adds IDE deployment target selection (includes physical device identifier). |
Files not reviewed (2)
- .idea/deploymentTargetSelector.xml: Language not supported
- .idea/gradle.xml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #Fri Mar 13 09:10:32 PST 2026 | ||
| distributionBase=GRADLE_USER_HOME | ||
| distributionPath=wrapper/dists | ||
| distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip | ||
| distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78 | ||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip |
| } catch (e: BiometricNotAvailableException) { | ||
| // Re-throw only if device genuinely has no screen lock at all. | ||
| // BIOMETRIC_ERROR_UNSUPPORTED on older Android = API limitation, not | ||
| // a real absence of screen lock — fall through to let the prompt show. | ||
| val msg = e.message ?: "" | ||
| if (!msg.contains("unsupported", ignoreCase = true) && | ||
| !msg.contains("code: 12", ignoreCase = true) // BIOMETRIC_ERROR_UNSUPPORTED = 12 | ||
| ) { | ||
| throw e | ||
| } |
There was a problem hiding this comment.
@chenchan09 please separate changes to key storage from this PR. This needs to be for just the provenance module.
| } catch (e: Throwable) { | ||
| Result.failure(Exception(e.message ?: e.javaClass.simpleName, e)) | ||
| } |
| val attester = SdkConfig.currentSigner?.publicKey | ||
| val schemaPda = foundation.metaplex.solanapublickeys.PublicKey(option.schemaId) | ||
| val recipientKey = if (option.recipient.isBlank()) attester | ||
| else foundation.metaplex.solanapublickeys.PublicKey(option.recipient) | ||
| val attestationId = if (attester != null && recipientKey != null) { | ||
| AttestationProgram.deriveAttestationAddress( | ||
| schema = schemaPda, | ||
| attester = attester, | ||
| recipient = recipientKey, | ||
| nonce = option.nonce | ||
| ).toBase58() | ||
| } else "" |
| object AttestationProgram { | ||
|
|
||
| /** On-chain program address for the Solana Attestation Service. */ | ||
| val PROGRAM_ID = PublicKey("22zoJMtdu5rJOmEMCqaYzDVLEqJa4FcFBGmrnEcUhbCr") | ||
|
|
||
| // ── Anchor discriminators (sha256("global:<name>")[0..8] little-endian) ─── | ||
| // Pre-computed from the SAS IDL. | ||
| private val DISC_CREATE_SCHEMA = byteArrayOf(102.toByte(), 82.toByte(), 205.toByte(), 109.toByte(), 56.toByte(), 206.toByte(), 253.toByte(), 189.toByte()) | ||
| private val DISC_CREATE_ATTESTATION = byteArrayOf( 71.toByte(), 149.toByte(), 77.toByte(), 206.toByte(), 166.toByte(), 86.toByte(), 252.toByte(), 185.toByte()) | ||
| private val DISC_REVOKE_ATTESTATION = byteArrayOf(243.toByte(), 175.toByte(), 179.toByte(), 26.toByte(), 67.toByte(), 213.toByte(), 154.toByte(), 97.toByte()) | ||
|
|
||
| // ── PDA seeds ───────────────────────────────────────────────────────────── | ||
|
|
||
| /** Derives the Schema PDA: seeds = ["schema", authority, name] */ | ||
| suspend fun deriveSchemaAddress(authority: PublicKey, name: String): PublicKey { | ||
| val seeds = listOf( | ||
| "schema".toByteArray(StandardCharsets.UTF_8), | ||
| authority.toByteArray(), | ||
| name.toByteArray(StandardCharsets.UTF_8) | ||
| ) | ||
| return PublicKey.findProgramAddress(seeds, PROGRAM_ID).address | ||
| } |
There was a problem hiding this comment.
@chenchan09 this doesn't need to be part of core, its only necessary for provenance.
| @Suppress("UNUSED_PARAMETER") | ||
| fun initializeKeystore(context: Context, appId: String, requireBiometric: Boolean = true) { | ||
| try { | ||
| val masterKey = MasterKey.Builder(context, MASTER_KEY_ALIAS) | ||
| .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) | ||
| .build() | ||
| buildMasterKey(context) | ||
| } catch (e: Exception) { | ||
| if (isKeyPermanentlyInvalidated(e)) throw BiometricInvalidatedException(cause = e) | ||
| throw VaultInitFailedException("Failed to initialize keystore: ${e.message}", cause = e) | ||
| } |
| fun clearVault(context: Context, appId: String): Boolean { | ||
| val vaultFile = File(context.filesDir, "$VAULT_FILE_PREFIX${appId}$VAULT_FILE_SUFFIX") | ||
| return vaultFile.delete() | ||
| purgeVault(context, appId) | ||
| return true |
| val attester = SdkConfig.currentSigner?.publicKey | ||
| val schemaId = attester?.let { | ||
| AttestationProgram.deriveSchemaAddress(it, option.name).toBase58() | ||
| } ?: "" |
|
@mocolicious I've opened a new pull request, #45, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@mocolicious I've opened a new pull request, #46, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@mocolicious I've opened a new pull request, #47, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: mocolicious <6373607+mocolicious@users.noreply.github.com>
…erve coroutine cancellation Co-authored-by: mocolicious <6373607+mocolicious@users.noreply.github.com>
Update PR #44 description to reflect full scope of changes
Fix: catch Exception instead of Throwable in suspend APIs to preserve coroutine cancellation
Fix PDA derivation ignoring per-call signer override in attest/createSchema
mocolicious
left a comment
There was a problem hiding this comment.
@chenchan09 there are several issues here.
- We shouldnt be creating schema
- The provenance changes belong in the provenance module. This includes the references to the program.
- Key storage changes need to be merged separately so we need to remove from this PR so we can continue to work on those separately.
- Address issues highlighted by copilot
I'd say start with the API and come back to this.
add solana attestation service