From fde4fa448609bf07c9d5d0f0be55bd1984a6fdf5 Mon Sep 17 00:00:00 2001 From: tolgahan-arikan Date: Fri, 4 Sep 2026 17:57:33 +0300 Subject: [PATCH 01/10] feat!: align wallet sessions, Solana, and wallet import Refresh the internal WaaS client and align the owner-facing SDK surface with TypeScript. Add Solana wallet and indexer support plus attested wallet import, while excluding backend RAC operations and example infrastructure. BREAKING CHANGE: access grant models and owner access methods now follow the direct and remote access session API. --- AGENTS.md | 4 + README.md | 98 +- docs/api-groups.conf | 47 +- docs/api.md | 604 +++++++++- docs/error-contracts.md | 11 +- gradle/libs.versions.toml | 4 + oms-wallet-kotlin-sdk/api/public-api.txt | 710 ++++++++++-- oms-wallet-kotlin-sdk/build.gradle.kts | 5 + .../technology/polygon/omswallet/Network.kt | 12 + .../technology/polygon/omswallet/OMSWallet.kt | 6 + .../polygon/omswallet/OMSWalletError.kt | 33 + .../polygon/omswallet/ParsedPublishableKey.kt | 2 + .../omswallet/WalletImportConfiguration.kt | 22 + .../omswallet/indexer/IndexerClient.kt | 152 ++- .../generated/waas/WaasWalletClient.kt | 1032 +++++++++++++++-- .../omswallet/models/OMSWalletModels.kt | 107 +- .../omswallet/models/SolanaIndexerModels.kt | 89 ++ .../omswallet/models/WalletImportModels.kt | 59 + .../omswallet/network/OMSWalletEnvironment.kt | 10 +- .../omswallet/network/OMSWalletHttpClient.kt | 4 +- .../omswallet/session/OMSWalletSession.kt | 8 + .../omswallet/wallet/AttestationVerifier.kt | 183 +++ .../omswallet/wallet/WalletAuthResult.kt | 6 +- .../polygon/omswallet/wallet/WalletClient.kt | 837 ++++++++++++- .../omswallet/wallet/WalletImportCrypto.kt | 137 +++ .../omswallet/PublicErrorContractsTest.kt | 5 +- .../omswallet/network/ServiceClientsTest.kt | 42 +- .../omswallet/wallet/WalletAccessTest.kt | 112 +- .../wallet/WalletClientTestFixtures.kt | 11 + .../omswallet/wallet/WalletEmailAuthTest.kt | 8 +- .../wallet/WalletImportCryptoTest.kt | 57 + .../omswallet/wallet/WalletTransactionTest.kt | 52 +- 32 files changed, 4185 insertions(+), 284 deletions(-) create mode 100644 oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/WalletImportConfiguration.kt create mode 100644 oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/SolanaIndexerModels.kt create mode 100644 oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/WalletImportModels.kt create mode 100644 oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/AttestationVerifier.kt create mode 100644 oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletImportCrypto.kt create mode 100644 oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletImportCryptoTest.kt diff --git a/AGENTS.md b/AGENTS.md index 11082da..03db812 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,6 +22,10 @@ documentation over training-data recall. If context7 is unavailable, use officia package sources and note the fallback; do not block ordinary repo work just to install extra tooling. +Wallet-import HPKE and attestation verification use Bouncy Castle and CBOR Java, with versions +owned by `gradle/libs.versions.toml`; consult their pinned-version APIs before changing the crypto, +certificate, CBOR, or COSE boundaries. + --- ## Project Overview diff --git a/README.md b/README.md index 8e8863f..59cb6f8 100644 --- a/README.md +++ b/README.md @@ -337,6 +337,35 @@ omsWallet.wallet.signOut() Keystore cleanup fails. Handle `OMSWalletStorageException` to report or retry a persistent cleanup failure. +### Import a Wallet + +Configure wallet import with audited AWS Nitro Enclave PCR0 measurements. The SDK rejects all-zero +debug measurements, verifies the attestation and request/response binding, and encrypts plaintext +keys locally before import. + +```kotlin +val omsWallet = + OMSWallet( + context = context, + publishableKey = "your-publishable-key", + walletImport = WalletImportConfiguration( + trustedPcr0s = listOf("your-audited-48-byte-pcr0-hex"), + ), + ) + +val imported = + omsWallet.wallet.importWallet( + privateKey = WalletImportPrivateKey.Ethereum("0x..."), + reference = "Imported wallet", + ) +println(imported.wallet.keyOrigin == WalletKeyOrigin.Imported) +``` + +Ethereum imports accept 32 raw bytes or hexadecimal text. Solana imports accept a 32-byte seed, +64-byte keypair, or base58 text. The SDK does not persist plaintext imported keys. For +caller-managed HPKE, use `getWalletImportRecipientKey` followed by `importEncryptedWallet`; both +responses remain attestation verified. + ## Core Workflows ### Sign and Verify Messages @@ -358,6 +387,17 @@ val verifyResult = omsWallet.wallet.isValidMessageSignature( ) ``` +For a selected Solana wallet, off-chain messages use the Solana-specific methods and do not require +a cluster: + +```kotlin +val signature = omsWallet.wallet.signSolanaMessage("hello from Solana") +val valid = omsWallet.wallet.isValidSolanaMessageSignature( + message = "hello from Solana", + signature = signature, +) +``` + ### Sign Typed Data ```kotlin @@ -464,6 +504,17 @@ tokenBalances.balances.forEach { balance -> } ``` +Query native SOL and fungible token balances through the Solana indexer gateway: + +```kotlin +val result = + omsWallet.indexer.getSolanaBalances( + walletAddress = "solana-wallet-address", + networks = listOf(SolanaNetwork.Mainnet, SolanaNetwork.Devnet), + ) +result.balances.forEach(::println) +``` + Pass `includeMetadata = true` when you need token contract details or NFT/token metadata from `balance.contractInfo` and `balance.tokenMetadata`. @@ -555,6 +606,19 @@ To refresh a transaction later: val status = omsWallet.wallet.getTransactionStatus(txnId = txResult.txnId) ``` +For a selected Solana wallet, amounts are smallest units (lamports for SOL and base units for SPL +tokens): + +```kotlin +val result = + omsWallet.wallet.sendSolanaTransfer( + network = SolanaNetwork.Devnet, + asset = "SOL", + to = "solana-recipient-address", + amount = BigInteger("1000000"), + ) +``` + ## Reference ### Errors @@ -603,12 +667,40 @@ val scopedIdToken = val credentials = omsWallet.wallet.listAccess(pageSize = 25u) omsWallet.wallet.listAccessPages(pageSize = 25u).collect { page -> - println(page.credentials) + println(page.grants) } credentials - .firstOrNull { !it.isCaller } - ?.let { omsWallet.wallet.revokeAccess(targetCredentialId = it.credentialId) } + .firstOrNull { !it.credential.isCaller } + ?.let { omsWallet.wallet.revokeAccess(credentialId = it.credential.credentialId) } +``` + +For an owner-approved smart session, inspect the remote credential before showing consent, then +authorize bounded EVM transfer grants. Backend credential registration and execution stay outside +this SDK surface. + +```kotlin +val credentialId = "remote-credential-id" +val metadata = omsWallet.wallet.inspectRemoteCredential(credentialId) +showConsentScreen(metadata) + +val session = + omsWallet.wallet.authorizeRemoteAccess( + credentialId = credentialId, + network = Network.POLYGON, + grants = + listOf( + SmartSessionGrant.NativeTransfer( + to = "0x1111111111111111111111111111111111111111", + limit = BigInteger("1000000000000000"), + ), + ), + expiresAt = "2099-01-01T00:00:00Z", + ) + +val details = omsWallet.wallet.getRemoteAccessSession(session.sessionId) +val usage = omsWallet.wallet.getRemoteAccessSessionUsage(session.sessionId, Network.POLYGON) +omsWallet.wallet.revokeAccess(credentialId, session.sessionId) ``` ## API Reference diff --git a/docs/api-groups.conf b/docs/api-groups.conf index 110c59e..79668dc 100644 --- a/docs/api-groups.conf +++ b/docs/api-groups.conf @@ -10,9 +10,30 @@ technology.polygon.omswallet.OMSWalletSessionState = OMSWalletSessionState technology.polygon.omswallet.OMSWalletSessionExpiredEvent = OMSWalletSessionExpiredEvent technology.polygon.omswallet.models.WalletType = WalletType technology.polygon.omswallet.models.Wallet = Wallet +technology.polygon.omswallet.models.WalletKeyOrigin = WalletKeyOrigin technology.polygon.omswallet.models.Page = Page -technology.polygon.omswallet.models.CredentialInfo = CredentialInfo -technology.polygon.omswallet.models.ListAccessResponse = ListAccessResponse +technology.polygon.omswallet.WalletImportConfiguration = WalletImportConfiguration +technology.polygon.omswallet.models.WalletImportCipherSuite = WalletImportCipherSuite +technology.polygon.omswallet.models.WalletImportPrivateKey = WalletImportPrivateKey +technology.polygon.omswallet.models.WalletImportPrivateKey.Ethereum = WalletImportPrivateKey.Ethereum +technology.polygon.omswallet.models.WalletImportPrivateKey.EthereumBytes = WalletImportPrivateKey.EthereumBytes +technology.polygon.omswallet.models.WalletImportPrivateKey.Solana = WalletImportPrivateKey.Solana +technology.polygon.omswallet.models.WalletImportPrivateKey.SolanaBytes = WalletImportPrivateKey.SolanaBytes +technology.polygon.omswallet.models.WalletImportRecipientKey = WalletImportRecipientKey +technology.polygon.omswallet.models.EncryptedWalletImportKeyMaterial = EncryptedWalletImportKeyMaterial +technology.polygon.omswallet.models.WalletCredential = WalletCredential +technology.polygon.omswallet.models.RemoteCredentialMetadata = RemoteCredentialMetadata +technology.polygon.omswallet.models.SmartSessionGrant = SmartSessionGrant +technology.polygon.omswallet.models.SmartSessionGrant.NativeTransfer = SmartSessionGrant.NativeTransfer +technology.polygon.omswallet.models.SmartSessionGrant.Erc20Transfer = SmartSessionGrant.Erc20Transfer +technology.polygon.omswallet.models.AccessGrantType = AccessGrantType +technology.polygon.omswallet.models.AccessGrant = AccessGrant +technology.polygon.omswallet.models.AccessGrant.Direct = AccessGrant.Direct +technology.polygon.omswallet.models.AccessGrant.Remote = AccessGrant.Remote +technology.polygon.omswallet.models.AccessGrantPage = AccessGrantPage +technology.polygon.omswallet.models.AuthorizedRemoteAccess = AuthorizedRemoteAccess +technology.polygon.omswallet.models.RemoteAccessSession = RemoteAccessSession +technology.polygon.omswallet.models.SmartSessionGrantUsage = SmartSessionGrantUsage technology.polygon.omswallet.wallet.CustomOidcProviderConfig = CustomOidcProviderConfig technology.polygon.omswallet.wallet.OmsRelayOidcProvider = OmsRelayOidcProvider technology.polygon.omswallet.wallet.OmsRelayOidcProviders = OmsRelayOidcProviders @@ -42,10 +63,17 @@ technology.polygon.omswallet.wallet.WalletClient#handleOidcRedirectCallback = Wa technology.polygon.omswallet.wallet.WalletClient#completeEmailAuth = WalletClient.completeEmailAuth technology.polygon.omswallet.wallet.WalletClient#useWallet = WalletClient.useWallet technology.polygon.omswallet.wallet.WalletClient#createWallet = WalletClient.createWallet +technology.polygon.omswallet.wallet.WalletClient#importWallet = WalletClient.importWallet +technology.polygon.omswallet.wallet.WalletClient#getWalletImportRecipientKey = WalletClient.getWalletImportRecipientKey +technology.polygon.omswallet.wallet.WalletClient#importEncryptedWallet = WalletClient.importEncryptedWallet technology.polygon.omswallet.wallet.WalletClient#listWallets = WalletClient.listWallets +technology.polygon.omswallet.wallet.WalletClient#inspectRemoteCredential = WalletClient.inspectRemoteCredential +technology.polygon.omswallet.wallet.WalletClient#authorizeRemoteAccess = WalletClient.authorizeRemoteAccess technology.polygon.omswallet.wallet.WalletClient#listAccess = WalletClient.listAccess technology.polygon.omswallet.wallet.WalletClient#listAccessPages = WalletClient.listAccessPages technology.polygon.omswallet.wallet.WalletClient#listAccessPage = WalletClient.listAccessPage +technology.polygon.omswallet.wallet.WalletClient#getRemoteAccessSession = WalletClient.getRemoteAccessSession +technology.polygon.omswallet.wallet.WalletClient#getRemoteAccessSessionUsage = WalletClient.getRemoteAccessSessionUsage technology.polygon.omswallet.wallet.WalletClient#getIdToken = WalletClient.getIdToken technology.polygon.omswallet.wallet.WalletClient#revokeAccess = WalletClient.revokeAccess @@ -64,10 +92,13 @@ technology.polygon.omswallet.models.SendTransactionResponse = SendTransactionRes technology.polygon.omswallet.models.TransactionStatusResolution = TransactionStatusResolution technology.polygon.omswallet.models.TransactionStatusPollingOptions = TransactionStatusPollingOptions technology.polygon.omswallet.wallet.WalletClient#signMessage = WalletClient.signMessage +technology.polygon.omswallet.wallet.WalletClient#signSolanaMessage = WalletClient.signSolanaMessage technology.polygon.omswallet.wallet.WalletClient#signTypedData = WalletClient.signTypedData technology.polygon.omswallet.wallet.WalletClient#isValidMessageSignature = WalletClient.isValidMessageSignature +technology.polygon.omswallet.wallet.WalletClient#isValidSolanaMessageSignature = WalletClient.isValidSolanaMessageSignature technology.polygon.omswallet.wallet.WalletClient#isValidTypedDataSignature = WalletClient.isValidTypedDataSignature technology.polygon.omswallet.wallet.WalletClient#sendTransaction = WalletClient.sendTransaction +technology.polygon.omswallet.wallet.WalletClient#sendSolanaTransfer = WalletClient.sendSolanaTransfer technology.polygon.omswallet.wallet.WalletClient#callContract = WalletClient.callContract technology.polygon.omswallet.wallet.WalletClient#getTransactionStatus = WalletClient.getTransactionStatus @@ -75,6 +106,15 @@ technology.polygon.omswallet.wallet.WalletClient#getTransactionStatus = WalletCl technology.polygon.omswallet.indexer.IndexerClient = IndexerClient technology.polygon.omswallet.indexer.IndexerClient#getBalances = IndexerClient.getBalances technology.polygon.omswallet.indexer.IndexerClient#getTransactionHistory = IndexerClient.getTransactionHistory +technology.polygon.omswallet.indexer.IndexerClient#getSolanaBalances = IndexerClient.getSolanaBalances +technology.polygon.omswallet.models.SolanaVerificationStatus = SolanaVerificationStatus +technology.polygon.omswallet.models.SolanaVerificationSource = SolanaVerificationSource +technology.polygon.omswallet.models.SolanaTokenProgram = SolanaTokenProgram +technology.polygon.omswallet.models.SolanaBalance = SolanaBalance +technology.polygon.omswallet.models.SolanaBalance.Native = SolanaBalance.Native +technology.polygon.omswallet.models.SolanaBalance.FungibleToken = SolanaBalance.FungibleToken +technology.polygon.omswallet.models.SolanaNetworkError = SolanaNetworkError +technology.polygon.omswallet.models.SolanaBalancesResult = SolanaBalancesResult technology.polygon.omswallet.models.TokenBalancesPageRequest = TokenBalancesPageRequest technology.polygon.omswallet.models.IndexerNetworkType = IndexerNetworkType technology.polygon.omswallet.models.ContractVerificationStatus = ContractVerificationStatus @@ -94,6 +134,8 @@ technology.polygon.omswallet.models.TransactionHistoryResult = TransactionHistor [Networks, types, and errors] technology.polygon.omswallet.Network = Network technology.polygon.omswallet.OMSWalletNetworks = OMSWalletNetworks +technology.polygon.omswallet.SolanaNetwork = SolanaNetwork +technology.polygon.omswallet.SolanaNetworks = SolanaNetworks technology.polygon.omswallet.OMSWalletErrorCode = OMSWalletErrorCode technology.polygon.omswallet.OMSWalletOperation = OMSWalletOperation technology.polygon.omswallet.OMSWalletUpstreamService = OMSWalletUpstreamService @@ -106,5 +148,6 @@ technology.polygon.omswallet.OMSWalletTransactionException = OMSWalletTransactio technology.polygon.omswallet.OMSWalletSelectionException = OMSWalletSelectionException technology.polygon.omswallet.OMSWalletValidationException = OMSWalletValidationException technology.polygon.omswallet.OMSWalletStorageException = OMSWalletStorageException +technology.polygon.omswallet.OMSWalletAttestationException = OMSWalletAttestationException technology.polygon.omswallet.utils#formatUnits = formatUnits technology.polygon.omswallet.utils#parseUnits = parseUnits diff --git a/docs/api.md b/docs/api.md index bf2f1d5..26bed1d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -22,6 +22,7 @@ class OMSWallet { context: Context, publishableKey: String, okHttpClient: OkHttpClient = OkHttpClient(), + walletImport: WalletImportConfiguration? = null, ) } ``` @@ -103,6 +104,7 @@ enum class WalletType( val wireValue: String, ) { Ethereum("ethereum"), + Solana("solana"), UNKNOWN_DEFAULT("UNKNOWN_DEFAULT"), } ``` @@ -115,9 +117,24 @@ data class Wallet( val type: WalletType, val address: String, val reference: String? = null, + val keyOrigin: WalletKeyOrigin, ) ``` +### `WalletKeyOrigin` + +Whether a wallet key was created in WaaS custody or imported by its owner. + +```kotlin +enum class WalletKeyOrigin( + val wireValue: String, +) { + Enclave("enclave"), + Imported("imported"), + UNKNOWN_DEFAULT("UNKNOWN_DEFAULT"), +} +``` + ### `Page` ```kotlin @@ -127,25 +144,256 @@ data class Page( ) ``` -### `CredentialInfo` +### `WalletImportConfiguration` + +Trust policy used to verify attested wallet-import responses. + +```kotlin +class WalletImportConfiguration( + trustedPcr0s: Collection, +) +``` + +### `WalletImportCipherSuite` + +HPKE cipher suites accepted by the wallet-import transport. + +```kotlin +enum class WalletImportCipherSuite( + val wireValue: String, +) { + X25519Sha256Aes256Gcm("x25519-sha256-aes256gcm"), + X25519Sha256ChaCha20Poly1305("x25519-sha256-chacha20poly1305"), + P256Sha256Aes256Gcm("p256-sha256-aes256gcm"), + P256Sha256ChaCha20Poly1305("p256-sha256-chacha20poly1305"), +} +``` + +### `WalletImportPrivateKey` + +Plaintext private-key input for high-level wallet import. + +```kotlin +sealed interface WalletImportPrivateKey { + val walletType: WalletType +} +``` + +### `WalletImportPrivateKey.Ethereum` + +Ethereum private key supplied as hexadecimal text. + +```kotlin +data class Ethereum( + val value: String, +) : WalletImportPrivateKey { + override val walletType: WalletType +} +``` + +### `WalletImportPrivateKey.EthereumBytes` + +Ethereum private key supplied as 32 raw bytes. ```kotlin -data class CredentialInfo( +class EthereumBytes( + val value: ByteArray, +) : WalletImportPrivateKey { + override val walletType: WalletType +} +``` + +### `WalletImportPrivateKey.Solana` + +Solana seed or keypair supplied as base58 text. + +```kotlin +data class Solana( + val value: String, +) : WalletImportPrivateKey { + override val walletType: WalletType +} +``` + +### `WalletImportPrivateKey.SolanaBytes` + +Solana seed or keypair supplied as 32 or 64 raw bytes. + +```kotlin +class SolanaBytes( + val value: ByteArray, +) : WalletImportPrivateKey { + override val walletType: WalletType +} +``` + +### `WalletImportRecipientKey` + +Attested public key returned for an advanced wallet-import encryption flow. + +```kotlin +data class WalletImportRecipientKey( + val keyId: String, + val cipherSuite: WalletImportCipherSuite, + val publicKey: String, +) +``` + +### `EncryptedWalletImportKeyMaterial` + +Caller-encrypted private-key material accepted by advanced wallet import. + +```kotlin +data class EncryptedWalletImportKeyMaterial( + val keyId: String, + val cipherSuite: WalletImportCipherSuite, + val encapsulatedKey: String, + val ciphertext: String, +) +``` + +### `WalletCredential` + +A credential currently authorized to use the selected wallet. + +```kotlin +data class WalletCredential( val credentialId: String, val expiresAt: String, val isCaller: Boolean, ) ``` -### `ListAccessResponse` +### `RemoteCredentialMetadata` + +Display metadata supplied by a remote application credential. + +```kotlin +data class RemoteCredentialMetadata( + val appUrl: String, + val appName: String, + val appLogoUrl: String, + val custom: Map, +) +``` + +### `SmartSessionGrant` + +Owner-approved EVM operation allowed during a bounded smart session. + +```kotlin +sealed interface SmartSessionGrant +``` + +### `SmartSessionGrant.NativeTransfer` + +```kotlin +data class NativeTransfer( + val to: String, + val limit: BigInteger, +) : SmartSessionGrant +``` + +### `SmartSessionGrant.Erc20Transfer` + +```kotlin +data class Erc20Transfer( + val token: String, + val to: String? = null, + val limit: BigInteger, + val cumulative: Boolean? = null, +) : SmartSessionGrant +``` + +### `AccessGrantType` + +Filter for direct or remotely authorized wallet access. + +```kotlin +enum class AccessGrantType { + Direct, + Remote, +} +``` + +### `AccessGrant` + +Direct or remote credential access associated with a wallet. + +```kotlin +sealed interface AccessGrant { + val credential: WalletCredential +} +``` + +### `AccessGrant.Direct` ```kotlin -data class ListAccessResponse( - val credentials: List, +data class Direct( + override val credential: WalletCredential, +) : AccessGrant +``` + +### `AccessGrant.Remote` + +```kotlin +data class Remote( + override val credential: WalletCredential, + val sessionId: String, + val metadata: RemoteCredentialMetadata, + val grants: List, +) : AccessGrant +``` + +### `AccessGrantPage` + +One page of wallet access grants and its continuation cursor. + +```kotlin +data class AccessGrantPage( + val grants: List, val page: Page? = null, ) ``` +### `AuthorizedRemoteAccess` + +Identifiers returned after an owner authorizes a remote smart session. + +```kotlin +data class AuthorizedRemoteAccess( + val walletId: String, + val sessionId: String, + val expiresAt: String, +) +``` + +### `RemoteAccessSession` + +Owner-visible details for one authorized smart session. + +```kotlin +data class RemoteAccessSession( + val sessionId: String, + val walletId: String, + val signerAddress: String, + val grants: List, + val chainId: Int, + val expiresAt: String, +) +``` + +### `SmartSessionGrantUsage` + +Current usage for one bounded smart-session grant. + +```kotlin +data class SmartSessionGrantUsage( + val grant: SmartSessionGrant, + val used: BigInteger? = null, +) +``` + ### `CustomOidcProviderConfig` Caller-owned OIDC provider configuration for authorization-code redirect auth. @@ -278,7 +526,7 @@ class PendingWalletSelection { val wallets: List - val credential: CredentialInfo + val credential: WalletCredential /** * Selects one of `wallets` and persists it as the active wallet session. @@ -309,7 +557,7 @@ data class WalletSelected( val walletAddress: String, val wallet: Wallet, val wallets: List, - val credential: CredentialInfo, + val credential: WalletCredential, ) : CompleteAuthResult ``` @@ -458,6 +706,37 @@ suspend fun createWallet( ): WalletSelectionResult ``` +### `WalletClient.importWallet` + +Imports and activates an Ethereum or Solana private key through the attested import transport. + +```kotlin +suspend fun importWallet( + privateKey: WalletImportPrivateKey, + reference: String? = null, +): WalletSelectionResult +``` + +### `WalletClient.getWalletImportRecipientKey` + +Fetches an attested recipient key for caller-managed wallet-import encryption. + +```kotlin +suspend fun getWalletImportRecipientKey(cipherSuite: WalletImportCipherSuite): WalletImportRecipientKey +``` + +### `WalletClient.importEncryptedWallet` + +Imports and activates caller-encrypted private-key material. + +```kotlin +suspend fun importEncryptedWallet( + walletType: WalletType, + keyMaterial: EncryptedWalletImportKeyMaterial, + reference: String? = null, +): WalletSelectionResult +``` + ### `WalletClient.listWallets` Lists all wallets available to the authenticated credential. @@ -466,12 +745,37 @@ Lists all wallets available to the authenticated credential. suspend fun listWallets(): List ``` +### `WalletClient.inspectRemoteCredential` + +Returns display metadata for a remote credential before the owner approves access. + +```kotlin +suspend fun inspectRemoteCredential(credentialId: String): RemoteCredentialMetadata +``` + +### `WalletClient.authorizeRemoteAccess` + +Authorizes owner-approved EVM smart-session grants for a remote credential. + +```kotlin +suspend fun authorizeRemoteAccess( + credentialId: String, + network: Network, + grants: List, + expiresAt: String, + sessionId: String? = null, +): AuthorizedRemoteAccess +``` + ### `WalletClient.listAccess` -Returns all credentials that currently have access to the selected wallet. +Returns all access grants, following WaaS cursors with the requested `pageSize`. ```kotlin -suspend fun listAccess(pageSize: UInt? = null): List +suspend fun listAccess( + pageSize: UInt? = null, + type: AccessGrantType? = null, +): List ``` ### `WalletClient.listAccessPages` @@ -480,7 +784,10 @@ Emits credential-access pages for the selected wallet until WaaS stops returning a cursor. ```kotlin -fun listAccessPages(pageSize: UInt? = null): Flow +fun listAccessPages( + pageSize: UInt? = null, + type: AccessGrantType? = null, +): Flow ``` ### `WalletClient.listAccessPage` @@ -491,7 +798,27 @@ Returns one credential-access page for the selected wallet. suspend fun listAccessPage( pageSize: UInt? = null, cursor: String? = null, -): ListAccessResponse + type: AccessGrantType? = null, +): AccessGrantPage +``` + +### `WalletClient.getRemoteAccessSession` + +Returns one owner-visible smart-session and checks that it belongs to the active wallet. + +```kotlin +suspend fun getRemoteAccessSession(sessionId: String): RemoteAccessSession +``` + +### `WalletClient.getRemoteAccessSessionUsage` + +Returns grant usage for an owner-visible smart session on `network`. + +```kotlin +suspend fun getRemoteAccessSessionUsage( + sessionId: String, + network: Network, +): List ``` ### `WalletClient.getIdToken` @@ -510,7 +837,10 @@ suspend fun getIdToken( Revokes a credential's access to the selected wallet. ```kotlin -suspend fun revokeAccess(targetCredentialId: String): Unit +suspend fun revokeAccess( + credentialId: String, + sessionId: String? = null, +): Unit ``` ## Transactions and signing @@ -571,8 +901,9 @@ data class FeeOption( ```kotlin data class FeeOptionSelection( val token: String, + val index: UInt? = null, ) { - constructor(feeOption: FeeOption) + constructor(feeOption: FeeOption, index: UInt? = null) } ``` @@ -615,13 +946,12 @@ fun interface FeeOptionSelector { ```kotlin data class FeeOptionWithBalance( val feeOption: FeeOption, - val balance: TokenBalance?, - val available: String?, - val availableRaw: String?, - val decimals: Int?, -) { - val selection: FeeOptionSelection -} + val selection: FeeOptionSelection = FeeOptionSelection(feeOption), + val balance: TokenBalance? = null, + val available: String? = null, + val availableRaw: String? = null, + val decimals: Int? = null, +) ``` ### `SendTransactionRequest` @@ -683,6 +1013,14 @@ suspend fun signMessage( ): String ``` +### `WalletClient.signSolanaMessage` + +Signs `message` with the currently selected Solana wallet. + +```kotlin +suspend fun signSolanaMessage(message: String): String +``` + ### `WalletClient.signTypedData` Signs EIP-712 `typedData` with the currently selected wallet on `network`. @@ -706,6 +1044,17 @@ suspend fun isValidMessageSignature( ): Boolean ``` +### `WalletClient.isValidSolanaMessageSignature` + +Validates `signature` for a Solana `message` through the WaaS public wallet RPC. + +```kotlin +suspend fun isValidSolanaMessageSignature( + message: String, + signature: String, +): Boolean +``` + ### `WalletClient.isValidTypedDataSignature` Validates `signature` for EIP-712 `typedData` through the WaaS public wallet RPC. @@ -741,6 +1090,23 @@ suspend fun sendTransaction( ): SendTransactionResponse ``` +### `WalletClient.sendSolanaTransfer` + +Sends a native SOL or SPL token transfer from the selected Solana wallet. + +```kotlin +suspend fun sendSolanaTransfer( + network: SolanaNetwork, + asset: String, + to: String, + amount: BigInteger, + mode: TransactionMode = TransactionMode.Relayer, + waitForStatus: Boolean = true, + statusPolling: TransactionStatusPollingOptions? = null, + selectFeeOption: FeeOptionSelector? = null, +): SendTransactionResponse +``` + ### `WalletClient.callContract` Calls a state-changing smart contract function through the WaaS @@ -816,6 +1182,159 @@ suspend fun getTransactionHistory( ): TransactionHistoryResult ``` +### `IndexerClient.getSolanaBalances` + +Gets native SOL and fungible-token balances for `walletAddress`. + +```kotlin +suspend fun getSolanaBalances( + walletAddress: String, + networks: List = listOf(SolanaNetwork.Mainnet, SolanaNetwork.Devnet), + includeMetadata: Boolean = true, + omitNativeBalances: Boolean? = null, + mintAddresses: List = emptyList(), + excludedMintAddresses: List = emptyList(), +): SolanaBalancesResult +``` + +### `SolanaVerificationStatus` + +Verification state assigned to Solana asset metadata. + +```kotlin +enum class SolanaVerificationStatus { + Verified, + Unverified, + Unknown, +} +``` + +### `SolanaVerificationSource` + +Source used to verify Solana asset metadata. + +```kotlin +enum class SolanaVerificationSource { + Jupiter, + SolflareUtl, + None, +} +``` + +### `SolanaTokenProgram` + +Token program owning a Solana mint. + +```kotlin +enum class SolanaTokenProgram { + SplToken, + Token2022, +} +``` + +### `SolanaBalance` + +Common public fields returned for a Solana balance. + +```kotlin +sealed interface SolanaBalance { + val network: SolanaNetwork + + val accountAddress: String + + val name: String + + val symbol: String + + val decimals: Int + + val balance: String + + val formattedBalance: String + + val imageUrl: String? + + val metadataUri: String? + + val verificationStatus: SolanaVerificationStatus + + val verificationSource: SolanaVerificationSource + + val priceUSD: String? + + val balanceUSD: String? +} +``` + +### `SolanaBalance.Native` + +Native SOL balance. + +```kotlin +data class Native( + override val network: SolanaNetwork, + override val accountAddress: String, + override val name: String, + override val symbol: String, + override val decimals: Int, + override val balance: String, + override val formattedBalance: String, + override val imageUrl: String?, + override val metadataUri: String?, + override val verificationStatus: SolanaVerificationStatus, + override val verificationSource: SolanaVerificationSource, + override val priceUSD: String?, + override val balanceUSD: String?, +) : SolanaBalance +``` + +### `SolanaBalance.FungibleToken` + +SPL Token or Token-2022 balance. + +```kotlin +data class FungibleToken( + override val network: SolanaNetwork, + override val accountAddress: String, + val tokenProgram: SolanaTokenProgram, + val mintAddress: String, + override val name: String, + override val symbol: String, + override val decimals: Int, + override val balance: String, + override val formattedBalance: String, + override val imageUrl: String?, + override val metadataUri: String?, + override val verificationStatus: SolanaVerificationStatus, + override val verificationSource: SolanaVerificationSource, + override val priceUSD: String?, + override val balanceUSD: String?, +) : SolanaBalance +``` + +### `SolanaNetworkError` + +Per-network failure returned alongside partial Solana balance results. + +```kotlin +data class SolanaNetworkError( + val network: SolanaNetwork, + val reason: String, +) +``` + +### `SolanaBalancesResult` + +Solana balances and partial network errors returned by the gateway. + +```kotlin +data class SolanaBalancesResult( + val status: Int, + val balances: List, + val errors: List, +) +``` + ### `TokenBalancesPageRequest` ```kotlin @@ -1134,6 +1653,27 @@ object OMSWalletNetworks { } ``` +### `SolanaNetwork` + +```kotlin +enum class SolanaNetwork( + val wireValue: String, +) { + Devnet("solana:devnet"), + Mainnet("solana:mainnet"), +} +``` + +### `SolanaNetworks` + +```kotlin +object SolanaNetworks { + val DEVNET: SolanaNetwork + + val MAINNET: SolanaNetwork +} +``` + ### `OMSWalletErrorCode` Stable SDK-level error categories for app-facing error handling. @@ -1155,6 +1695,7 @@ enum class OMSWalletErrorCode( TransactionStatusLookupFailed("OMS_TRANSACTION_STATUS_LOOKUP_FAILED"), ValidationError("OMS_VALIDATION_ERROR"), StorageError("OMS_STORAGE_ERROR"), + AttestationVerificationFailed("OMS_ATTESTATION_VERIFICATION_FAILED"), } ``` @@ -1170,24 +1711,35 @@ enum class OMSWalletOperation( PendingWalletSelectionCreateAndSelectWallet("wallet.pendingWalletSelection.createAndSelectWallet"), PendingWalletSelectionSelectWallet("wallet.pendingWalletSelection.selectWallet"), IndexerGetBalances("indexer.getBalances"), + IndexerGetSolanaBalances("indexer.getSolanaBalances"), IndexerGetTransactionHistory("indexer.getTransactionHistory"), WalletCallContract("wallet.callContract"), + WalletAuthorizeRemoteAccess("wallet.authorizeRemoteAccess"), WalletCompleteEmailAuth("wallet.completeEmailAuth"), WalletCreateWallet("wallet.createWallet"), + WalletImportWallet("wallet.importWallet"), + WalletGetImportRecipientKey("wallet.getWalletImportRecipientKey"), + WalletImportEncryptedWallet("wallet.importEncryptedWallet"), WalletExecute("wallet.execute"), WalletGetIdToken("wallet.getIdToken"), + WalletGetRemoteAccessSession("wallet.getRemoteAccessSession"), + WalletGetRemoteAccessSessionUsage("wallet.getRemoteAccessSessionUsage"), WalletHandleOidcRedirectCallback("wallet.handleOidcRedirectCallback"), WalletGetTransactionStatus("wallet.getTransactionStatus"), WalletIsValidMessageSignature("wallet.isValidMessageSignature"), + WalletIsValidSolanaMessageSignature("wallet.isValidSolanaMessageSignature"), WalletIsValidTypedDataSignature("wallet.isValidTypedDataSignature"), + WalletInspectRemoteCredential("wallet.inspectRemoteCredential"), WalletListAccess("wallet.listAccess"), WalletListAccessPage("wallet.listAccessPage"), WalletListAccessPages("wallet.listAccessPages"), WalletListWallets("wallet.listWallets"), WalletRevokeAccess("wallet.revokeAccess"), WalletSendTransaction("wallet.sendTransaction"), + WalletSendSolanaTransfer("wallet.sendSolanaTransfer"), WalletSignInWithOidcIdToken("wallet.signInWithOidcIdToken"), WalletSignMessage("wallet.signMessage"), + WalletSignSolanaMessage("wallet.signSolanaMessage"), WalletSignOut("wallet.signOut"), WalletSignTypedData("wallet.signTypedData"), WalletStartEmailAuth("wallet.startEmailAuth"), @@ -1322,6 +1874,18 @@ class OMSWalletStorageException( ) : OMSWalletException ``` +### `OMSWalletAttestationException` + +Thrown when a wallet-import response cannot be authenticated as an approved enclave. + +```kotlin +class OMSWalletAttestationException( + operation: OMSWalletOperation? = null, + message: String, + cause: Throwable? = null, +) : OMSWalletException +``` + ### `formatUnits` Divides `value` by 10^`decimals` and formats it as a decimal string. diff --git a/docs/error-contracts.md b/docs/error-contracts.md index 95a8684..a624023 100644 --- a/docs/error-contracts.md +++ b/docs/error-contracts.md @@ -47,13 +47,14 @@ whether `upstreamError` should be present, and which tests own the contract. | OIDC redirect/id-token auth methods | Local OIDC config, callback, or state mismatch | `OMSWalletSessionException` or `OMSWalletValidationException` | Fix redirect config/state or restart OIDC flow | Absent | `PublicErrorContractsTest` | | `client.wallet.startOidcRedirectAuth` | Local OIDC redirect-state persistence failure | `OMSWalletStorageException`, `OMS_STORAGE_ERROR` | Retry starting OIDC auth after the local storage issue is resolved | Absent | `PublicErrorContractsTest` | | `client.wallet.signOut` | Persistent session, redirect-state, or signer cleanup failure | `OMSWalletStorageException`, `OMS_STORAGE_ERROR`; in-memory session is already cleared | Keep the user signed out locally; report or retry persistent cleanup as appropriate | Absent | `WalletSessionTest` | -| Protected wallet methods: `getIdToken`, `signMessage`, `signTypedData`, `sendTransaction`, `callContract`, `getTransactionStatus`, `listAccess`, `listAccessPages`, `revokeAccess` | Missing, expired, or stale local session | `OMSWalletSessionException` | Authenticate again or recover local session; no remote request was made | Absent | `PublicErrorContractsTest` | -| `client.wallet.startEmailAuth`, `signMessage`, `signTypedData`, `getIdToken`, `sendTransaction`, `callContract` | SDK-local validation or fee-selection failure | `OMSWalletValidationException` | Correct parameters or local fee selection; do not retry as an upstream outage | Absent | `PublicErrorContractsTest` | +| Protected wallet methods: `getIdToken`, signing and transaction methods, wallet import, `getTransactionStatus`, access inspection/authorization/usage/listing/revocation | Missing, expired, or stale local session | `OMSWalletSessionException` | Authenticate again or recover local session; no remote request was made | Absent | `PublicErrorContractsTest` | +| Wallet auth, signing, transactions, import, and owner access methods | SDK-local validation or fee-selection failure | `OMSWalletValidationException` | Correct parameters or local fee selection; do not retry as an upstream outage | Absent | `PublicErrorContractsTest`, `WalletImportCryptoTest`, `WalletAccessTest` | +| `client.wallet.getWalletImportRecipientKey`, `importWallet`, `importEncryptedWallet` | Recipient-key attestation is missing, stale, malformed, untrusted, or does not match the request/response | `OMSWalletAttestationException`, `OMS_ATTESTATION_VERIFICATION_FAILED` | Do not encrypt or submit key material; retry only after confirming the configured PCR0 allowlist and WaaS environment | Absent | `WalletImportCryptoTest` | | `client.wallet.isValidMessageSignature`, `isValidTypedDataSignature` | WaaS validation backend failure | `OMSWalletRequestException` or `OMSWalletResponseException` with validation operation | Retry based on SDK code/status; log upstream detail | Present | `PublicErrorContractsTest` | | `client.wallet.sendTransaction`, `callContract` | Execute request fails after prepare | `OMSWalletTransactionException`, `OMS_TRANSACTION_EXECUTION_UNCONFIRMED`, `retryable = false`, `txnId` | Do not blindly resend the write; preserve `txnId` and upstream detail for diagnostics | Present when execute crossed transport/upstream boundary | `PublicErrorContractsTest` | | `client.wallet.sendTransaction`, `callContract` | Submitted transaction status polling fails | `OMSWalletTransactionException`, `OMS_TRANSACTION_STATUS_LOOKUP_FAILED`, `retryable = true`, `txnId` | Retry status lookup, not the original write | Present when polling crossed transport/upstream boundary | `PublicErrorContractsTest` | | `client.wallet.getTransactionStatus` | Direct status lookup backend failure | `OMSWalletRequestException` or `OMSWalletResponseException` with status operation | Retry status lookup or surface backend status to the user | Present | `PublicErrorContractsTest` | -| `client.wallet.listAccess`, `listAccessPages`, `revokeAccess` | WaaS access backend failure | `OMSWalletRequestException` or `OMSWalletResponseException` with access operation | Retry based on SDK code/status; log upstream detail | Present | `PublicErrorContractsTest` | -| `client.indexer.getBalances`, `getTransactionHistory` | IndexerGateway backend, transport, malformed JSON, or malformed payload | `OMSWalletRequestException` or `OMSWalletResponseException` with indexer operation | Retry based on SDK code/status; log upstream detail | Present for remote/transport response failures | `PublicErrorContractsTest` | -| `client.indexer.getBalances`, `getTransactionHistory` | IndexerGateway non-JSON HTTP body | `OMSWalletRequestException`, `OMS_HTTP_ERROR`, sanitized message | Do not expose raw upstream HTML/text bodies; log normalized detail | Present, sanitized | `PublicErrorContractsTest` | +| `client.wallet.inspectRemoteCredential`, `authorizeRemoteAccess`, `getRemoteAccessSession`, `getRemoteAccessSessionUsage`, access listing and revocation | WaaS owner-access backend failure | `OMSWalletRequestException` or `OMSWalletResponseException` with access operation | Retry based on SDK code/status; log upstream detail | Present | `PublicErrorContractsTest`, `WalletAccessTest` | +| `client.indexer.getBalances`, `getTransactionHistory`, `getSolanaBalances` | IndexerGateway backend, transport, malformed JSON, or malformed payload | `OMSWalletRequestException` or `OMSWalletResponseException` with indexer operation | Retry based on SDK code/status; log upstream detail | Present for remote/transport response failures | `PublicErrorContractsTest`, `ServiceClientsTest` | +| `client.indexer.getBalances`, `getTransactionHistory`, `getSolanaBalances` | IndexerGateway non-JSON HTTP body | `OMSWalletRequestException`, `OMS_HTTP_ERROR`, sanitized message | Do not expose raw upstream HTML/text bodies; log normalized detail | Present, sanitized | `PublicErrorContractsTest` | | Public `OMSWalletException` classes and upstream fields | Error class field contract | Stable public fields on constructed errors | Use only when the error class/helper is the unit under test | As constructed | `PublicErrorContractsTest` | diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d9c67ac..0131101 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,8 @@ kotlinCompilerEmbeddable = "2.2.10" ktlintGradle = "14.2.0" ktlint = "1.8.0" browser = "1.8.0" +bouncyCastle = "1.85" +cbor = "4.4.4" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -36,6 +38,8 @@ androidx-credentials-play-services-auth = { group = "androidx.credentials", name googleid = { group = "com.google.android.libraries.identity.googleid", name = "googleid", version.ref = "googleid" } androidx-browser = { group = "androidx.browser", name = "browser", version.ref = "browser" } kotlin-compiler-embeddable = { group = "org.jetbrains.kotlin", name = "kotlin-compiler-embeddable", version.ref = "kotlinCompilerEmbeddable" } +bouncy-castle = { group = "org.bouncycastle", name = "bcprov-jdk18on", version.ref = "bouncyCastle" } +cbor = { group = "com.upokecenter", name = "cbor", version.ref = "cbor" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/oms-wallet-kotlin-sdk/api/public-api.txt b/oms-wallet-kotlin-sdk/api/public-api.txt index 6e442e9..dd0a91e 100644 --- a/oms-wallet-kotlin-sdk/api/public-api.txt +++ b/oms-wallet-kotlin-sdk/api/public-api.txt @@ -55,15 +55,15 @@ public final class technology.polygon.omswallet.OMSWallet { public static final technology.polygon.omswallet.OMSWallet$Companion Companion; public final technology.polygon.omswallet.wallet.WalletClient getWallet(); public final technology.polygon.omswallet.indexer.IndexerClient getIndexer(); - public technology.polygon.omswallet.OMSWallet(android.content.Context, java.lang.String, okhttp3.OkHttpClient); - public technology.polygon.omswallet.OMSWallet(android.content.Context, java.lang.String, okhttp3.OkHttpClient, int, kotlin.jvm.internal.DefaultConstructorMarker); - public technology.polygon.omswallet.OMSWallet(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, okhttp3.OkHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, technology.polygon.omswallet.wallet.CredentialSigner, java.lang.String, kotlin.jvm.internal.DefaultConstructorMarker); + public technology.polygon.omswallet.OMSWallet(android.content.Context, java.lang.String, okhttp3.OkHttpClient, technology.polygon.omswallet.WalletImportConfiguration); + public technology.polygon.omswallet.OMSWallet(android.content.Context, java.lang.String, okhttp3.OkHttpClient, technology.polygon.omswallet.WalletImportConfiguration, int, kotlin.jvm.internal.DefaultConstructorMarker); + public technology.polygon.omswallet.OMSWallet(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, okhttp3.OkHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, technology.polygon.omswallet.wallet.CredentialSigner, java.lang.String, technology.polygon.omswallet.WalletImportConfiguration, kotlin.jvm.internal.DefaultConstructorMarker); } Compiled from "OMSWallet.kt" public final class technology.polygon.omswallet.OMSWallet$Companion { - public final technology.polygon.omswallet.OMSWallet createForTesting$oms_wallet_kotlin_sdk(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, okhttp3.OkHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, technology.polygon.omswallet.wallet.CredentialSigner, java.lang.String); - public static technology.polygon.omswallet.OMSWallet createForTesting$oms_wallet_kotlin_sdk$default(technology.polygon.omswallet.OMSWallet$Companion, java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, okhttp3.OkHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, technology.polygon.omswallet.wallet.CredentialSigner, java.lang.String, int, java.lang.Object); + public final technology.polygon.omswallet.OMSWallet createForTesting$oms_wallet_kotlin_sdk(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, okhttp3.OkHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, technology.polygon.omswallet.wallet.CredentialSigner, java.lang.String, technology.polygon.omswallet.WalletImportConfiguration); + public static technology.polygon.omswallet.OMSWallet createForTesting$oms_wallet_kotlin_sdk$default(technology.polygon.omswallet.OMSWallet$Companion, java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, okhttp3.OkHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, technology.polygon.omswallet.wallet.CredentialSigner, java.lang.String, technology.polygon.omswallet.WalletImportConfiguration, int, java.lang.Object); public final java.lang.String scopedSessionFileName$oms_wallet_kotlin_sdk(java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment); public final java.lang.String scopedCredentialKeyAlias$oms_wallet_kotlin_sdk(java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment); public final java.lang.String scopedCredentialNonceStoreName$oms_wallet_kotlin_sdk(java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment); @@ -78,6 +78,12 @@ public final class technology.polygon.omswallet.OMSWallet$Companion { public technology.polygon.omswallet.OMSWallet$Companion(kotlin.jvm.internal.DefaultConstructorMarker); } +Compiled from "OMSWalletError.kt" +public final class technology.polygon.omswallet.OMSWalletAttestationException extends technology.polygon.omswallet.OMSWalletException { + public technology.polygon.omswallet.OMSWalletAttestationException(technology.polygon.omswallet.OMSWalletOperation, java.lang.String, java.lang.Throwable); + public technology.polygon.omswallet.OMSWalletAttestationException(technology.polygon.omswallet.OMSWalletOperation, java.lang.String, java.lang.Throwable, int, kotlin.jvm.internal.DefaultConstructorMarker); +} + Compiled from "OMSWalletSessionState.kt" public final class technology.polygon.omswallet.OMSWalletEmailSessionAuth implements technology.polygon.omswallet.OMSWalletSessionAuth { public technology.polygon.omswallet.OMSWalletEmailSessionAuth(java.lang.String); @@ -105,6 +111,7 @@ public final class technology.polygon.omswallet.OMSWalletErrorCode extends java. public static final technology.polygon.omswallet.OMSWalletErrorCode TransactionStatusLookupFailed; public static final technology.polygon.omswallet.OMSWalletErrorCode ValidationError; public static final technology.polygon.omswallet.OMSWalletErrorCode StorageError; + public static final technology.polygon.omswallet.OMSWalletErrorCode AttestationVerificationFailed; public final java.lang.String getId(); public static technology.polygon.omswallet.OMSWalletErrorCode[] values(); public static technology.polygon.omswallet.OMSWalletErrorCode valueOf(java.lang.String); @@ -172,24 +179,35 @@ public final class technology.polygon.omswallet.OMSWalletOperation extends java. public static final technology.polygon.omswallet.OMSWalletOperation PendingWalletSelectionCreateAndSelectWallet; public static final technology.polygon.omswallet.OMSWalletOperation PendingWalletSelectionSelectWallet; public static final technology.polygon.omswallet.OMSWalletOperation IndexerGetBalances; + public static final technology.polygon.omswallet.OMSWalletOperation IndexerGetSolanaBalances; public static final technology.polygon.omswallet.OMSWalletOperation IndexerGetTransactionHistory; public static final technology.polygon.omswallet.OMSWalletOperation WalletCallContract; + public static final technology.polygon.omswallet.OMSWalletOperation WalletAuthorizeRemoteAccess; public static final technology.polygon.omswallet.OMSWalletOperation WalletCompleteEmailAuth; public static final technology.polygon.omswallet.OMSWalletOperation WalletCreateWallet; + public static final technology.polygon.omswallet.OMSWalletOperation WalletImportWallet; + public static final technology.polygon.omswallet.OMSWalletOperation WalletGetImportRecipientKey; + public static final technology.polygon.omswallet.OMSWalletOperation WalletImportEncryptedWallet; public static final technology.polygon.omswallet.OMSWalletOperation WalletExecute; public static final technology.polygon.omswallet.OMSWalletOperation WalletGetIdToken; + public static final technology.polygon.omswallet.OMSWalletOperation WalletGetRemoteAccessSession; + public static final technology.polygon.omswallet.OMSWalletOperation WalletGetRemoteAccessSessionUsage; public static final technology.polygon.omswallet.OMSWalletOperation WalletHandleOidcRedirectCallback; public static final technology.polygon.omswallet.OMSWalletOperation WalletGetTransactionStatus; public static final technology.polygon.omswallet.OMSWalletOperation WalletIsValidMessageSignature; + public static final technology.polygon.omswallet.OMSWalletOperation WalletIsValidSolanaMessageSignature; public static final technology.polygon.omswallet.OMSWalletOperation WalletIsValidTypedDataSignature; + public static final technology.polygon.omswallet.OMSWalletOperation WalletInspectRemoteCredential; public static final technology.polygon.omswallet.OMSWalletOperation WalletListAccess; public static final technology.polygon.omswallet.OMSWalletOperation WalletListAccessPage; public static final technology.polygon.omswallet.OMSWalletOperation WalletListAccessPages; public static final technology.polygon.omswallet.OMSWalletOperation WalletListWallets; public static final technology.polygon.omswallet.OMSWalletOperation WalletRevokeAccess; public static final technology.polygon.omswallet.OMSWalletOperation WalletSendTransaction; + public static final technology.polygon.omswallet.OMSWalletOperation WalletSendSolanaTransfer; public static final technology.polygon.omswallet.OMSWalletOperation WalletSignInWithOidcIdToken; public static final technology.polygon.omswallet.OMSWalletOperation WalletSignMessage; + public static final technology.polygon.omswallet.OMSWalletOperation WalletSignSolanaMessage; public static final technology.polygon.omswallet.OMSWalletOperation WalletSignOut; public static final technology.polygon.omswallet.OMSWalletOperation WalletSignTypedData; public static final technology.polygon.omswallet.OMSWalletOperation WalletStartEmailAuth; @@ -316,15 +334,18 @@ public final class technology.polygon.omswallet.OMSWalletValidationException ext Compiled from "ParsedPublishableKey.kt" public final class technology.polygon.omswallet.ParsedPublishableKey { - public technology.polygon.omswallet.ParsedPublishableKey(java.lang.String, java.lang.String, java.lang.String); + public technology.polygon.omswallet.ParsedPublishableKey(java.lang.String, java.lang.String, java.lang.String, java.lang.String); + public technology.polygon.omswallet.ParsedPublishableKey(java.lang.String, java.lang.String, java.lang.String, java.lang.String, int, kotlin.jvm.internal.DefaultConstructorMarker); public final java.lang.String getProjectId(); public final java.lang.String getWalletApiUrl(); public final java.lang.String getIndexerGatewayUrl(); + public final java.lang.String getSolanaIndexerGatewayUrl(); public final java.lang.String component1(); public final java.lang.String component2(); public final java.lang.String component3(); - public final technology.polygon.omswallet.ParsedPublishableKey copy(java.lang.String, java.lang.String, java.lang.String); - public static technology.polygon.omswallet.ParsedPublishableKey copy$default(technology.polygon.omswallet.ParsedPublishableKey, java.lang.String, java.lang.String, java.lang.String, int, java.lang.Object); + public final java.lang.String component4(); + public final technology.polygon.omswallet.ParsedPublishableKey copy(java.lang.String, java.lang.String, java.lang.String, java.lang.String); + public static technology.polygon.omswallet.ParsedPublishableKey copy$default(technology.polygon.omswallet.ParsedPublishableKey, java.lang.String, java.lang.String, java.lang.String, java.lang.String, int, java.lang.Object); public java.lang.String toString(); public int hashCode(); public boolean equals(java.lang.Object); @@ -335,6 +356,29 @@ public final class technology.polygon.omswallet.ParsedPublishableKeyKt { public static final technology.polygon.omswallet.ParsedPublishableKey parsePublishableKey(java.lang.String); } +Compiled from "Network.kt" +public final class technology.polygon.omswallet.SolanaNetwork extends java.lang.Enum { + public static final technology.polygon.omswallet.SolanaNetwork Devnet; + public static final technology.polygon.omswallet.SolanaNetwork Mainnet; + public final java.lang.String getWireValue(); + public static technology.polygon.omswallet.SolanaNetwork[] values(); + public static technology.polygon.omswallet.SolanaNetwork valueOf(java.lang.String); + public static kotlin.enums.EnumEntries getEntries(); +} + +Compiled from "Network.kt" +public final class technology.polygon.omswallet.SolanaNetworks { + public static final technology.polygon.omswallet.SolanaNetworks INSTANCE; + public final technology.polygon.omswallet.SolanaNetwork getDEVNET(); + public final technology.polygon.omswallet.SolanaNetwork getMAINNET(); +} + +Compiled from "WalletImportConfiguration.kt" +public final class technology.polygon.omswallet.WalletImportConfiguration { + public technology.polygon.omswallet.WalletImportConfiguration(java.util.Collection); + public final java.util.Set getTrustedPcr0s$oms_wallet_kotlin_sdk(); +} + Compiled from "IndexerClient.kt" public final class technology.polygon.omswallet.indexer.IndexerClient { public static final technology.polygon.omswallet.indexer.IndexerClient$Companion Companion; @@ -342,7 +386,9 @@ public final class technology.polygon.omswallet.indexer.IndexerClient { public static java.lang.Object getBalances$default(technology.polygon.omswallet.indexer.IndexerClient, java.lang.String, java.util.List, technology.polygon.omswallet.models.IndexerNetworkType, java.util.List, boolean, java.lang.Boolean, java.util.List, technology.polygon.omswallet.models.ContractVerificationStatus, technology.polygon.omswallet.models.TokenBalancesPageRequest, kotlin.coroutines.Continuation, int, java.lang.Object); public final java.lang.Object getTransactionHistory(java.lang.String, java.util.List, technology.polygon.omswallet.models.IndexerNetworkType, java.util.List, java.util.List, java.util.List, java.lang.Long, java.lang.Long, java.lang.String, boolean, java.lang.Boolean, technology.polygon.omswallet.models.MetadataOptions, technology.polygon.omswallet.models.TokenBalancesPageRequest, kotlin.coroutines.Continuation); public static java.lang.Object getTransactionHistory$default(technology.polygon.omswallet.indexer.IndexerClient, java.lang.String, java.util.List, technology.polygon.omswallet.models.IndexerNetworkType, java.util.List, java.util.List, java.util.List, java.lang.Long, java.lang.Long, java.lang.String, boolean, java.lang.Boolean, technology.polygon.omswallet.models.MetadataOptions, technology.polygon.omswallet.models.TokenBalancesPageRequest, kotlin.coroutines.Continuation, int, java.lang.Object); - public static final java.lang.Object access$postIndexerGatewayJson(technology.polygon.omswallet.indexer.IndexerClient, technology.polygon.omswallet.OMSWalletOperation, java.lang.String, java.lang.String, kotlin.coroutines.Continuation); + public final java.lang.Object getSolanaBalances(java.lang.String, java.util.List, boolean, java.lang.Boolean, java.util.List, java.util.List, kotlin.coroutines.Continuation); + public static java.lang.Object getSolanaBalances$default(technology.polygon.omswallet.indexer.IndexerClient, java.lang.String, java.util.List, boolean, java.lang.Boolean, java.util.List, java.util.List, kotlin.coroutines.Continuation, int, java.lang.Object); + public static final java.lang.Object access$postIndexerGatewayJson(technology.polygon.omswallet.indexer.IndexerClient, technology.polygon.omswallet.OMSWalletOperation, java.lang.String, java.lang.String, java.lang.String, java.lang.String, kotlin.coroutines.Continuation); public technology.polygon.omswallet.indexer.IndexerClient(java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, technology.polygon.omswallet.network.OMSWalletHttpClient, kotlin.jvm.internal.DefaultConstructorMarker); } @@ -367,6 +413,81 @@ public final class technology.polygon.omswallet.models.AbiArg { public boolean equals(java.lang.Object); } +Compiled from "OMSWalletModels.kt" +public interface technology.polygon.omswallet.models.AccessGrant { + public abstract technology.polygon.omswallet.models.WalletCredential getCredential(); +} + +Compiled from "OMSWalletModels.kt" +public final class technology.polygon.omswallet.models.AccessGrant$Direct implements technology.polygon.omswallet.models.AccessGrant { + public technology.polygon.omswallet.models.AccessGrant$Direct(technology.polygon.omswallet.models.WalletCredential); + public technology.polygon.omswallet.models.WalletCredential getCredential(); + public final technology.polygon.omswallet.models.WalletCredential component1(); + public final technology.polygon.omswallet.models.AccessGrant$Direct copy(technology.polygon.omswallet.models.WalletCredential); + public static technology.polygon.omswallet.models.AccessGrant$Direct copy$default(technology.polygon.omswallet.models.AccessGrant$Direct, technology.polygon.omswallet.models.WalletCredential, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "OMSWalletModels.kt" +public final class technology.polygon.omswallet.models.AccessGrant$Remote implements technology.polygon.omswallet.models.AccessGrant { + public technology.polygon.omswallet.models.AccessGrant$Remote(technology.polygon.omswallet.models.WalletCredential, java.lang.String, technology.polygon.omswallet.models.RemoteCredentialMetadata, java.util.List); + public technology.polygon.omswallet.models.WalletCredential getCredential(); + public final java.lang.String getSessionId(); + public final technology.polygon.omswallet.models.RemoteCredentialMetadata getMetadata(); + public final java.util.List getGrants(); + public final technology.polygon.omswallet.models.WalletCredential component1(); + public final java.lang.String component2(); + public final technology.polygon.omswallet.models.RemoteCredentialMetadata component3(); + public final java.util.List component4(); + public final technology.polygon.omswallet.models.AccessGrant$Remote copy(technology.polygon.omswallet.models.WalletCredential, java.lang.String, technology.polygon.omswallet.models.RemoteCredentialMetadata, java.util.List); + public static technology.polygon.omswallet.models.AccessGrant$Remote copy$default(technology.polygon.omswallet.models.AccessGrant$Remote, technology.polygon.omswallet.models.WalletCredential, java.lang.String, technology.polygon.omswallet.models.RemoteCredentialMetadata, java.util.List, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "OMSWalletModels.kt" +public final class technology.polygon.omswallet.models.AccessGrantPage { + public technology.polygon.omswallet.models.AccessGrantPage(java.util.List, technology.polygon.omswallet.models.Page); + public technology.polygon.omswallet.models.AccessGrantPage(java.util.List, technology.polygon.omswallet.models.Page, int, kotlin.jvm.internal.DefaultConstructorMarker); + public final java.util.List getGrants(); + public final technology.polygon.omswallet.models.Page getPage(); + public final java.util.List component1(); + public final technology.polygon.omswallet.models.Page component2(); + public final technology.polygon.omswallet.models.AccessGrantPage copy(java.util.List, technology.polygon.omswallet.models.Page); + public static technology.polygon.omswallet.models.AccessGrantPage copy$default(technology.polygon.omswallet.models.AccessGrantPage, java.util.List, technology.polygon.omswallet.models.Page, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "OMSWalletModels.kt" +public final class technology.polygon.omswallet.models.AccessGrantType extends java.lang.Enum { + public static final technology.polygon.omswallet.models.AccessGrantType Direct; + public static final technology.polygon.omswallet.models.AccessGrantType Remote; + public static technology.polygon.omswallet.models.AccessGrantType[] values(); + public static technology.polygon.omswallet.models.AccessGrantType valueOf(java.lang.String); + public static kotlin.enums.EnumEntries getEntries(); +} + +Compiled from "OMSWalletModels.kt" +public final class technology.polygon.omswallet.models.AuthorizedRemoteAccess { + public technology.polygon.omswallet.models.AuthorizedRemoteAccess(java.lang.String, java.lang.String, java.lang.String); + public final java.lang.String getWalletId(); + public final java.lang.String getSessionId(); + public final java.lang.String getExpiresAt(); + public final java.lang.String component1(); + public final java.lang.String component2(); + public final java.lang.String component3(); + public final technology.polygon.omswallet.models.AuthorizedRemoteAccess copy(java.lang.String, java.lang.String, java.lang.String); + public static technology.polygon.omswallet.models.AuthorizedRemoteAccess copy$default(technology.polygon.omswallet.models.AuthorizedRemoteAccess, java.lang.String, java.lang.String, java.lang.String, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + Compiled from "OMSWalletModels.kt" public final class technology.polygon.omswallet.models.ContractTokenBalance implements technology.polygon.omswallet.models.TokenBalance { public technology.polygon.omswallet.models.ContractTokenBalance(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, long, long, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.Boolean, technology.polygon.omswallet.models.TokenContractInfo, technology.polygon.omswallet.models.TokenMetadata); @@ -419,17 +540,19 @@ public final class technology.polygon.omswallet.models.ContractVerificationStatu public static kotlin.enums.EnumEntries getEntries(); } -Compiled from "OMSWalletModels.kt" -public final class technology.polygon.omswallet.models.CredentialInfo { - public technology.polygon.omswallet.models.CredentialInfo(java.lang.String, java.lang.String, boolean); - public final java.lang.String getCredentialId(); - public final java.lang.String getExpiresAt(); - public final boolean isCaller(); +Compiled from "WalletImportModels.kt" +public final class technology.polygon.omswallet.models.EncryptedWalletImportKeyMaterial { + public technology.polygon.omswallet.models.EncryptedWalletImportKeyMaterial(java.lang.String, technology.polygon.omswallet.models.WalletImportCipherSuite, java.lang.String, java.lang.String); + public final java.lang.String getKeyId(); + public final technology.polygon.omswallet.models.WalletImportCipherSuite getCipherSuite(); + public final java.lang.String getEncapsulatedKey(); + public final java.lang.String getCiphertext(); public final java.lang.String component1(); - public final java.lang.String component2(); - public final boolean component3(); - public final technology.polygon.omswallet.models.CredentialInfo copy(java.lang.String, java.lang.String, boolean); - public static technology.polygon.omswallet.models.CredentialInfo copy$default(technology.polygon.omswallet.models.CredentialInfo, java.lang.String, java.lang.String, boolean, int, java.lang.Object); + public final technology.polygon.omswallet.models.WalletImportCipherSuite component2(); + public final java.lang.String component3(); + public final java.lang.String component4(); + public final technology.polygon.omswallet.models.EncryptedWalletImportKeyMaterial copy(java.lang.String, technology.polygon.omswallet.models.WalletImportCipherSuite, java.lang.String, java.lang.String); + public static technology.polygon.omswallet.models.EncryptedWalletImportKeyMaterial copy$default(technology.polygon.omswallet.models.EncryptedWalletImportKeyMaterial, java.lang.String, technology.polygon.omswallet.models.WalletImportCipherSuite, java.lang.String, java.lang.String, int, java.lang.Object); public java.lang.String toString(); public int hashCode(); public boolean equals(java.lang.Object); @@ -453,15 +576,19 @@ public final class technology.polygon.omswallet.models.FeeOption { Compiled from "OMSWalletModels.kt" public final class technology.polygon.omswallet.models.FeeOptionSelection { - public technology.polygon.omswallet.models.FeeOptionSelection(java.lang.String); + public technology.polygon.omswallet.models.FeeOptionSelection(java.lang.String, kotlin.UInt, int, kotlin.jvm.internal.DefaultConstructorMarker); public final java.lang.String getToken(); - public technology.polygon.omswallet.models.FeeOptionSelection(technology.polygon.omswallet.models.FeeOption); + public final kotlin.UInt getIndex-0hXNFcg(); + public technology.polygon.omswallet.models.FeeOptionSelection(technology.polygon.omswallet.models.FeeOption, kotlin.UInt, int, kotlin.jvm.internal.DefaultConstructorMarker); public final java.lang.String component1(); - public final technology.polygon.omswallet.models.FeeOptionSelection copy(java.lang.String); - public static technology.polygon.omswallet.models.FeeOptionSelection copy$default(technology.polygon.omswallet.models.FeeOptionSelection, java.lang.String, int, java.lang.Object); + public final kotlin.UInt component2-0hXNFcg(); + public final technology.polygon.omswallet.models.FeeOptionSelection copy-FrkygD8(java.lang.String, kotlin.UInt); + public static technology.polygon.omswallet.models.FeeOptionSelection copy-FrkygD8$default(technology.polygon.omswallet.models.FeeOptionSelection, java.lang.String, kotlin.UInt, int, java.lang.Object); public java.lang.String toString(); public int hashCode(); public boolean equals(java.lang.Object); + public technology.polygon.omswallet.models.FeeOptionSelection(java.lang.String, kotlin.UInt, kotlin.jvm.internal.DefaultConstructorMarker); + public technology.polygon.omswallet.models.FeeOptionSelection(technology.polygon.omswallet.models.FeeOption, kotlin.UInt, kotlin.jvm.internal.DefaultConstructorMarker); } Compiled from "OMSWalletModels.kt" @@ -477,20 +604,22 @@ public final class technology.polygon.omswallet.models.FeeOptionSelector$Compani Compiled from "OMSWalletModels.kt" public final class technology.polygon.omswallet.models.FeeOptionWithBalance { - public technology.polygon.omswallet.models.FeeOptionWithBalance(technology.polygon.omswallet.models.FeeOption, technology.polygon.omswallet.models.TokenBalance, java.lang.String, java.lang.String, java.lang.Integer); + public technology.polygon.omswallet.models.FeeOptionWithBalance(technology.polygon.omswallet.models.FeeOption, technology.polygon.omswallet.models.FeeOptionSelection, technology.polygon.omswallet.models.TokenBalance, java.lang.String, java.lang.String, java.lang.Integer); + public technology.polygon.omswallet.models.FeeOptionWithBalance(technology.polygon.omswallet.models.FeeOption, technology.polygon.omswallet.models.FeeOptionSelection, technology.polygon.omswallet.models.TokenBalance, java.lang.String, java.lang.String, java.lang.Integer, int, kotlin.jvm.internal.DefaultConstructorMarker); public final technology.polygon.omswallet.models.FeeOption getFeeOption(); + public final technology.polygon.omswallet.models.FeeOptionSelection getSelection(); public final technology.polygon.omswallet.models.TokenBalance getBalance(); public final java.lang.String getAvailable(); public final java.lang.String getAvailableRaw(); public final java.lang.Integer getDecimals(); - public final technology.polygon.omswallet.models.FeeOptionSelection getSelection(); public final technology.polygon.omswallet.models.FeeOption component1(); - public final technology.polygon.omswallet.models.TokenBalance component2(); - public final java.lang.String component3(); + public final technology.polygon.omswallet.models.FeeOptionSelection component2(); + public final technology.polygon.omswallet.models.TokenBalance component3(); public final java.lang.String component4(); - public final java.lang.Integer component5(); - public final technology.polygon.omswallet.models.FeeOptionWithBalance copy(technology.polygon.omswallet.models.FeeOption, technology.polygon.omswallet.models.TokenBalance, java.lang.String, java.lang.String, java.lang.Integer); - public static technology.polygon.omswallet.models.FeeOptionWithBalance copy$default(technology.polygon.omswallet.models.FeeOptionWithBalance, technology.polygon.omswallet.models.FeeOption, technology.polygon.omswallet.models.TokenBalance, java.lang.String, java.lang.String, java.lang.Integer, int, java.lang.Object); + public final java.lang.String component5(); + public final java.lang.Integer component6(); + public final technology.polygon.omswallet.models.FeeOptionWithBalance copy(technology.polygon.omswallet.models.FeeOption, technology.polygon.omswallet.models.FeeOptionSelection, technology.polygon.omswallet.models.TokenBalance, java.lang.String, java.lang.String, java.lang.Integer); + public static technology.polygon.omswallet.models.FeeOptionWithBalance copy$default(technology.polygon.omswallet.models.FeeOptionWithBalance, technology.polygon.omswallet.models.FeeOption, technology.polygon.omswallet.models.FeeOptionSelection, technology.polygon.omswallet.models.TokenBalance, java.lang.String, java.lang.String, java.lang.Integer, int, java.lang.Object); public java.lang.String toString(); public int hashCode(); public boolean equals(java.lang.Object); @@ -534,21 +663,6 @@ public final class technology.polygon.omswallet.models.IndexerNetworkType extend public static kotlin.enums.EnumEntries getEntries(); } -Compiled from "OMSWalletModels.kt" -public final class technology.polygon.omswallet.models.ListAccessResponse { - public technology.polygon.omswallet.models.ListAccessResponse(java.util.List, technology.polygon.omswallet.models.Page); - public technology.polygon.omswallet.models.ListAccessResponse(java.util.List, technology.polygon.omswallet.models.Page, int, kotlin.jvm.internal.DefaultConstructorMarker); - public final java.util.List getCredentials(); - public final technology.polygon.omswallet.models.Page getPage(); - public final java.util.List component1(); - public final technology.polygon.omswallet.models.Page component2(); - public final technology.polygon.omswallet.models.ListAccessResponse copy(java.util.List, technology.polygon.omswallet.models.Page); - public static technology.polygon.omswallet.models.ListAccessResponse copy$default(technology.polygon.omswallet.models.ListAccessResponse, java.util.List, technology.polygon.omswallet.models.Page, int, java.lang.Object); - public java.lang.String toString(); - public int hashCode(); - public boolean equals(java.lang.Object); -} - Compiled from "OMSWalletModels.kt" public final class technology.polygon.omswallet.models.MetadataOptions { public technology.polygon.omswallet.models.MetadataOptions(java.lang.Boolean, java.lang.Boolean, java.util.List); @@ -616,6 +730,46 @@ public final class technology.polygon.omswallet.models.Page { public technology.polygon.omswallet.models.Page(kotlin.UInt, java.lang.String, kotlin.jvm.internal.DefaultConstructorMarker); } +Compiled from "OMSWalletModels.kt" +public final class technology.polygon.omswallet.models.RemoteAccessSession { + public technology.polygon.omswallet.models.RemoteAccessSession(java.lang.String, java.lang.String, java.lang.String, java.util.List, int, java.lang.String); + public final java.lang.String getSessionId(); + public final java.lang.String getWalletId(); + public final java.lang.String getSignerAddress(); + public final java.util.List getGrants(); + public final int getChainId(); + public final java.lang.String getExpiresAt(); + public final java.lang.String component1(); + public final java.lang.String component2(); + public final java.lang.String component3(); + public final java.util.List component4(); + public final int component5(); + public final java.lang.String component6(); + public final technology.polygon.omswallet.models.RemoteAccessSession copy(java.lang.String, java.lang.String, java.lang.String, java.util.List, int, java.lang.String); + public static technology.polygon.omswallet.models.RemoteAccessSession copy$default(technology.polygon.omswallet.models.RemoteAccessSession, java.lang.String, java.lang.String, java.lang.String, java.util.List, int, java.lang.String, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "OMSWalletModels.kt" +public final class technology.polygon.omswallet.models.RemoteCredentialMetadata { + public technology.polygon.omswallet.models.RemoteCredentialMetadata(java.lang.String, java.lang.String, java.lang.String, java.util.Map); + public final java.lang.String getAppUrl(); + public final java.lang.String getAppName(); + public final java.lang.String getAppLogoUrl(); + public final java.util.Map getCustom(); + public final java.lang.String component1(); + public final java.lang.String component2(); + public final java.lang.String component3(); + public final java.util.Map component4(); + public final technology.polygon.omswallet.models.RemoteCredentialMetadata copy(java.lang.String, java.lang.String, java.lang.String, java.util.Map); + public static technology.polygon.omswallet.models.RemoteCredentialMetadata copy$default(technology.polygon.omswallet.models.RemoteCredentialMetadata, java.lang.String, java.lang.String, java.lang.String, java.util.Map, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + Compiled from "OMSWalletModels.kt" public final class technology.polygon.omswallet.models.SendTransactionRequest { public technology.polygon.omswallet.models.SendTransactionRequest(java.lang.String, java.math.BigInteger, java.lang.String, technology.polygon.omswallet.models.TransactionMode); @@ -653,6 +807,210 @@ public final class technology.polygon.omswallet.models.SendTransactionResponse { public boolean equals(java.lang.Object); } +Compiled from "OMSWalletModels.kt" +public interface technology.polygon.omswallet.models.SmartSessionGrant { +} + +Compiled from "OMSWalletModels.kt" +public final class technology.polygon.omswallet.models.SmartSessionGrant$Erc20Transfer implements technology.polygon.omswallet.models.SmartSessionGrant { + public technology.polygon.omswallet.models.SmartSessionGrant$Erc20Transfer(java.lang.String, java.lang.String, java.math.BigInteger, java.lang.Boolean); + public technology.polygon.omswallet.models.SmartSessionGrant$Erc20Transfer(java.lang.String, java.lang.String, java.math.BigInteger, java.lang.Boolean, int, kotlin.jvm.internal.DefaultConstructorMarker); + public final java.lang.String getToken(); + public final java.lang.String getTo(); + public final java.math.BigInteger getLimit(); + public final java.lang.Boolean getCumulative(); + public final java.lang.String component1(); + public final java.lang.String component2(); + public final java.math.BigInteger component3(); + public final java.lang.Boolean component4(); + public final technology.polygon.omswallet.models.SmartSessionGrant$Erc20Transfer copy(java.lang.String, java.lang.String, java.math.BigInteger, java.lang.Boolean); + public static technology.polygon.omswallet.models.SmartSessionGrant$Erc20Transfer copy$default(technology.polygon.omswallet.models.SmartSessionGrant$Erc20Transfer, java.lang.String, java.lang.String, java.math.BigInteger, java.lang.Boolean, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "OMSWalletModels.kt" +public final class technology.polygon.omswallet.models.SmartSessionGrant$NativeTransfer implements technology.polygon.omswallet.models.SmartSessionGrant { + public technology.polygon.omswallet.models.SmartSessionGrant$NativeTransfer(java.lang.String, java.math.BigInteger); + public final java.lang.String getTo(); + public final java.math.BigInteger getLimit(); + public final java.lang.String component1(); + public final java.math.BigInteger component2(); + public final technology.polygon.omswallet.models.SmartSessionGrant$NativeTransfer copy(java.lang.String, java.math.BigInteger); + public static technology.polygon.omswallet.models.SmartSessionGrant$NativeTransfer copy$default(technology.polygon.omswallet.models.SmartSessionGrant$NativeTransfer, java.lang.String, java.math.BigInteger, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "OMSWalletModels.kt" +public final class technology.polygon.omswallet.models.SmartSessionGrantUsage { + public technology.polygon.omswallet.models.SmartSessionGrantUsage(technology.polygon.omswallet.models.SmartSessionGrant, java.math.BigInteger); + public technology.polygon.omswallet.models.SmartSessionGrantUsage(technology.polygon.omswallet.models.SmartSessionGrant, java.math.BigInteger, int, kotlin.jvm.internal.DefaultConstructorMarker); + public final technology.polygon.omswallet.models.SmartSessionGrant getGrant(); + public final java.math.BigInteger getUsed(); + public final technology.polygon.omswallet.models.SmartSessionGrant component1(); + public final java.math.BigInteger component2(); + public final technology.polygon.omswallet.models.SmartSessionGrantUsage copy(technology.polygon.omswallet.models.SmartSessionGrant, java.math.BigInteger); + public static technology.polygon.omswallet.models.SmartSessionGrantUsage copy$default(technology.polygon.omswallet.models.SmartSessionGrantUsage, technology.polygon.omswallet.models.SmartSessionGrant, java.math.BigInteger, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "SolanaIndexerModels.kt" +public interface technology.polygon.omswallet.models.SolanaBalance { + public abstract technology.polygon.omswallet.SolanaNetwork getNetwork(); + public abstract java.lang.String getAccountAddress(); + public abstract java.lang.String getName(); + public abstract java.lang.String getSymbol(); + public abstract int getDecimals(); + public abstract java.lang.String getBalance(); + public abstract java.lang.String getFormattedBalance(); + public abstract java.lang.String getImageUrl(); + public abstract java.lang.String getMetadataUri(); + public abstract technology.polygon.omswallet.models.SolanaVerificationStatus getVerificationStatus(); + public abstract technology.polygon.omswallet.models.SolanaVerificationSource getVerificationSource(); + public abstract java.lang.String getPriceUSD(); + public abstract java.lang.String getBalanceUSD(); +} + +Compiled from "SolanaIndexerModels.kt" +public final class technology.polygon.omswallet.models.SolanaBalance$FungibleToken implements technology.polygon.omswallet.models.SolanaBalance { + public technology.polygon.omswallet.models.SolanaBalance$FungibleToken(technology.polygon.omswallet.SolanaNetwork, java.lang.String, technology.polygon.omswallet.models.SolanaTokenProgram, java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.models.SolanaVerificationStatus, technology.polygon.omswallet.models.SolanaVerificationSource, java.lang.String, java.lang.String); + public technology.polygon.omswallet.SolanaNetwork getNetwork(); + public java.lang.String getAccountAddress(); + public final technology.polygon.omswallet.models.SolanaTokenProgram getTokenProgram(); + public final java.lang.String getMintAddress(); + public java.lang.String getName(); + public java.lang.String getSymbol(); + public int getDecimals(); + public java.lang.String getBalance(); + public java.lang.String getFormattedBalance(); + public java.lang.String getImageUrl(); + public java.lang.String getMetadataUri(); + public technology.polygon.omswallet.models.SolanaVerificationStatus getVerificationStatus(); + public technology.polygon.omswallet.models.SolanaVerificationSource getVerificationSource(); + public java.lang.String getPriceUSD(); + public java.lang.String getBalanceUSD(); + public final technology.polygon.omswallet.SolanaNetwork component1(); + public final java.lang.String component2(); + public final technology.polygon.omswallet.models.SolanaTokenProgram component3(); + public final java.lang.String component4(); + public final java.lang.String component5(); + public final java.lang.String component6(); + public final int component7(); + public final java.lang.String component8(); + public final java.lang.String component9(); + public final java.lang.String component10(); + public final java.lang.String component11(); + public final technology.polygon.omswallet.models.SolanaVerificationStatus component12(); + public final technology.polygon.omswallet.models.SolanaVerificationSource component13(); + public final java.lang.String component14(); + public final java.lang.String component15(); + public final technology.polygon.omswallet.models.SolanaBalance$FungibleToken copy(technology.polygon.omswallet.SolanaNetwork, java.lang.String, technology.polygon.omswallet.models.SolanaTokenProgram, java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.models.SolanaVerificationStatus, technology.polygon.omswallet.models.SolanaVerificationSource, java.lang.String, java.lang.String); + public static technology.polygon.omswallet.models.SolanaBalance$FungibleToken copy$default(technology.polygon.omswallet.models.SolanaBalance$FungibleToken, technology.polygon.omswallet.SolanaNetwork, java.lang.String, technology.polygon.omswallet.models.SolanaTokenProgram, java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.models.SolanaVerificationStatus, technology.polygon.omswallet.models.SolanaVerificationSource, java.lang.String, java.lang.String, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "SolanaIndexerModels.kt" +public final class technology.polygon.omswallet.models.SolanaBalance$Native implements technology.polygon.omswallet.models.SolanaBalance { + public technology.polygon.omswallet.models.SolanaBalance$Native(technology.polygon.omswallet.SolanaNetwork, java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.models.SolanaVerificationStatus, technology.polygon.omswallet.models.SolanaVerificationSource, java.lang.String, java.lang.String); + public technology.polygon.omswallet.SolanaNetwork getNetwork(); + public java.lang.String getAccountAddress(); + public java.lang.String getName(); + public java.lang.String getSymbol(); + public int getDecimals(); + public java.lang.String getBalance(); + public java.lang.String getFormattedBalance(); + public java.lang.String getImageUrl(); + public java.lang.String getMetadataUri(); + public technology.polygon.omswallet.models.SolanaVerificationStatus getVerificationStatus(); + public technology.polygon.omswallet.models.SolanaVerificationSource getVerificationSource(); + public java.lang.String getPriceUSD(); + public java.lang.String getBalanceUSD(); + public final technology.polygon.omswallet.SolanaNetwork component1(); + public final java.lang.String component2(); + public final java.lang.String component3(); + public final java.lang.String component4(); + public final int component5(); + public final java.lang.String component6(); + public final java.lang.String component7(); + public final java.lang.String component8(); + public final java.lang.String component9(); + public final technology.polygon.omswallet.models.SolanaVerificationStatus component10(); + public final technology.polygon.omswallet.models.SolanaVerificationSource component11(); + public final java.lang.String component12(); + public final java.lang.String component13(); + public final technology.polygon.omswallet.models.SolanaBalance$Native copy(technology.polygon.omswallet.SolanaNetwork, java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.models.SolanaVerificationStatus, technology.polygon.omswallet.models.SolanaVerificationSource, java.lang.String, java.lang.String); + public static technology.polygon.omswallet.models.SolanaBalance$Native copy$default(technology.polygon.omswallet.models.SolanaBalance$Native, technology.polygon.omswallet.SolanaNetwork, java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.models.SolanaVerificationStatus, technology.polygon.omswallet.models.SolanaVerificationSource, java.lang.String, java.lang.String, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "SolanaIndexerModels.kt" +public final class technology.polygon.omswallet.models.SolanaBalancesResult { + public technology.polygon.omswallet.models.SolanaBalancesResult(int, java.util.List, java.util.List); + public final int getStatus(); + public final java.util.List getBalances(); + public final java.util.List getErrors(); + public final int component1(); + public final java.util.List component2(); + public final java.util.List component3(); + public final technology.polygon.omswallet.models.SolanaBalancesResult copy(int, java.util.List, java.util.List); + public static technology.polygon.omswallet.models.SolanaBalancesResult copy$default(technology.polygon.omswallet.models.SolanaBalancesResult, int, java.util.List, java.util.List, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "SolanaIndexerModels.kt" +public final class technology.polygon.omswallet.models.SolanaNetworkError { + public technology.polygon.omswallet.models.SolanaNetworkError(technology.polygon.omswallet.SolanaNetwork, java.lang.String); + public final technology.polygon.omswallet.SolanaNetwork getNetwork(); + public final java.lang.String getReason(); + public final technology.polygon.omswallet.SolanaNetwork component1(); + public final java.lang.String component2(); + public final technology.polygon.omswallet.models.SolanaNetworkError copy(technology.polygon.omswallet.SolanaNetwork, java.lang.String); + public static technology.polygon.omswallet.models.SolanaNetworkError copy$default(technology.polygon.omswallet.models.SolanaNetworkError, technology.polygon.omswallet.SolanaNetwork, java.lang.String, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "SolanaIndexerModels.kt" +public final class technology.polygon.omswallet.models.SolanaTokenProgram extends java.lang.Enum { + public static final technology.polygon.omswallet.models.SolanaTokenProgram SplToken; + public static final technology.polygon.omswallet.models.SolanaTokenProgram Token2022; + public static technology.polygon.omswallet.models.SolanaTokenProgram[] values(); + public static technology.polygon.omswallet.models.SolanaTokenProgram valueOf(java.lang.String); + public static kotlin.enums.EnumEntries getEntries(); +} + +Compiled from "SolanaIndexerModels.kt" +public final class technology.polygon.omswallet.models.SolanaVerificationSource extends java.lang.Enum { + public static final technology.polygon.omswallet.models.SolanaVerificationSource Jupiter; + public static final technology.polygon.omswallet.models.SolanaVerificationSource SolflareUtl; + public static final technology.polygon.omswallet.models.SolanaVerificationSource None; + public static technology.polygon.omswallet.models.SolanaVerificationSource[] values(); + public static technology.polygon.omswallet.models.SolanaVerificationSource valueOf(java.lang.String); + public static kotlin.enums.EnumEntries getEntries(); +} + +Compiled from "SolanaIndexerModels.kt" +public final class technology.polygon.omswallet.models.SolanaVerificationStatus extends java.lang.Enum { + public static final technology.polygon.omswallet.models.SolanaVerificationStatus Verified; + public static final technology.polygon.omswallet.models.SolanaVerificationStatus Unverified; + public static final technology.polygon.omswallet.models.SolanaVerificationStatus Unknown; + public static technology.polygon.omswallet.models.SolanaVerificationStatus[] values(); + public static technology.polygon.omswallet.models.SolanaVerificationStatus valueOf(java.lang.String); + public static kotlin.enums.EnumEntries getEntries(); +} + Compiled from "OMSWalletModels.kt" public interface technology.polygon.omswallet.models.TokenBalance { public abstract java.lang.String getContractType(); @@ -988,26 +1346,129 @@ public final class technology.polygon.omswallet.models.TransactionTransfer { Compiled from "OMSWalletModels.kt" public final class technology.polygon.omswallet.models.Wallet { - public technology.polygon.omswallet.models.Wallet(java.lang.String, technology.polygon.omswallet.models.WalletType, java.lang.String, java.lang.String); - public technology.polygon.omswallet.models.Wallet(java.lang.String, technology.polygon.omswallet.models.WalletType, java.lang.String, java.lang.String, int, kotlin.jvm.internal.DefaultConstructorMarker); + public technology.polygon.omswallet.models.Wallet(java.lang.String, technology.polygon.omswallet.models.WalletType, java.lang.String, java.lang.String, technology.polygon.omswallet.models.WalletKeyOrigin); + public technology.polygon.omswallet.models.Wallet(java.lang.String, technology.polygon.omswallet.models.WalletType, java.lang.String, java.lang.String, technology.polygon.omswallet.models.WalletKeyOrigin, int, kotlin.jvm.internal.DefaultConstructorMarker); public final java.lang.String getId(); public final technology.polygon.omswallet.models.WalletType getType(); public final java.lang.String getAddress(); public final java.lang.String getReference(); + public final technology.polygon.omswallet.models.WalletKeyOrigin getKeyOrigin(); public final java.lang.String component1(); public final technology.polygon.omswallet.models.WalletType component2(); public final java.lang.String component3(); public final java.lang.String component4(); - public final technology.polygon.omswallet.models.Wallet copy(java.lang.String, technology.polygon.omswallet.models.WalletType, java.lang.String, java.lang.String); - public static technology.polygon.omswallet.models.Wallet copy$default(technology.polygon.omswallet.models.Wallet, java.lang.String, technology.polygon.omswallet.models.WalletType, java.lang.String, java.lang.String, int, java.lang.Object); + public final technology.polygon.omswallet.models.WalletKeyOrigin component5(); + public final technology.polygon.omswallet.models.Wallet copy(java.lang.String, technology.polygon.omswallet.models.WalletType, java.lang.String, java.lang.String, technology.polygon.omswallet.models.WalletKeyOrigin); + public static technology.polygon.omswallet.models.Wallet copy$default(technology.polygon.omswallet.models.Wallet, java.lang.String, technology.polygon.omswallet.models.WalletType, java.lang.String, java.lang.String, technology.polygon.omswallet.models.WalletKeyOrigin, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "OMSWalletModels.kt" +public final class technology.polygon.omswallet.models.WalletCredential { + public technology.polygon.omswallet.models.WalletCredential(java.lang.String, java.lang.String, boolean); + public final java.lang.String getCredentialId(); + public final java.lang.String getExpiresAt(); + public final boolean isCaller(); + public final java.lang.String component1(); + public final java.lang.String component2(); + public final boolean component3(); + public final technology.polygon.omswallet.models.WalletCredential copy(java.lang.String, java.lang.String, boolean); + public static technology.polygon.omswallet.models.WalletCredential copy$default(technology.polygon.omswallet.models.WalletCredential, java.lang.String, java.lang.String, boolean, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "WalletImportModels.kt" +public final class technology.polygon.omswallet.models.WalletImportCipherSuite extends java.lang.Enum { + public static final technology.polygon.omswallet.models.WalletImportCipherSuite X25519Sha256Aes256Gcm; + public static final technology.polygon.omswallet.models.WalletImportCipherSuite X25519Sha256ChaCha20Poly1305; + public static final technology.polygon.omswallet.models.WalletImportCipherSuite P256Sha256Aes256Gcm; + public static final technology.polygon.omswallet.models.WalletImportCipherSuite P256Sha256ChaCha20Poly1305; + public final java.lang.String getWireValue(); + public static technology.polygon.omswallet.models.WalletImportCipherSuite[] values(); + public static technology.polygon.omswallet.models.WalletImportCipherSuite valueOf(java.lang.String); + public static kotlin.enums.EnumEntries getEntries(); +} + +Compiled from "WalletImportModels.kt" +public interface technology.polygon.omswallet.models.WalletImportPrivateKey { + public abstract technology.polygon.omswallet.models.WalletType getWalletType(); +} + +Compiled from "WalletImportModels.kt" +public final class technology.polygon.omswallet.models.WalletImportPrivateKey$Ethereum implements technology.polygon.omswallet.models.WalletImportPrivateKey { + public technology.polygon.omswallet.models.WalletImportPrivateKey$Ethereum(java.lang.String); + public final java.lang.String getValue(); + public technology.polygon.omswallet.models.WalletType getWalletType(); + public final java.lang.String component1(); + public final technology.polygon.omswallet.models.WalletImportPrivateKey$Ethereum copy(java.lang.String); + public static technology.polygon.omswallet.models.WalletImportPrivateKey$Ethereum copy$default(technology.polygon.omswallet.models.WalletImportPrivateKey$Ethereum, java.lang.String, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "WalletImportModels.kt" +public final class technology.polygon.omswallet.models.WalletImportPrivateKey$EthereumBytes implements technology.polygon.omswallet.models.WalletImportPrivateKey { + public technology.polygon.omswallet.models.WalletImportPrivateKey$EthereumBytes(byte[]); + public final byte[] getValue(); + public technology.polygon.omswallet.models.WalletType getWalletType(); +} + +Compiled from "WalletImportModels.kt" +public final class technology.polygon.omswallet.models.WalletImportPrivateKey$Solana implements technology.polygon.omswallet.models.WalletImportPrivateKey { + public technology.polygon.omswallet.models.WalletImportPrivateKey$Solana(java.lang.String); + public final java.lang.String getValue(); + public technology.polygon.omswallet.models.WalletType getWalletType(); + public final java.lang.String component1(); + public final technology.polygon.omswallet.models.WalletImportPrivateKey$Solana copy(java.lang.String); + public static technology.polygon.omswallet.models.WalletImportPrivateKey$Solana copy$default(technology.polygon.omswallet.models.WalletImportPrivateKey$Solana, java.lang.String, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "WalletImportModels.kt" +public final class technology.polygon.omswallet.models.WalletImportPrivateKey$SolanaBytes implements technology.polygon.omswallet.models.WalletImportPrivateKey { + public technology.polygon.omswallet.models.WalletImportPrivateKey$SolanaBytes(byte[]); + public final byte[] getValue(); + public technology.polygon.omswallet.models.WalletType getWalletType(); +} + +Compiled from "WalletImportModels.kt" +public final class technology.polygon.omswallet.models.WalletImportRecipientKey { + public technology.polygon.omswallet.models.WalletImportRecipientKey(java.lang.String, technology.polygon.omswallet.models.WalletImportCipherSuite, java.lang.String); + public final java.lang.String getKeyId(); + public final technology.polygon.omswallet.models.WalletImportCipherSuite getCipherSuite(); + public final java.lang.String getPublicKey(); + public final java.lang.String component1(); + public final technology.polygon.omswallet.models.WalletImportCipherSuite component2(); + public final java.lang.String component3(); + public final technology.polygon.omswallet.models.WalletImportRecipientKey copy(java.lang.String, technology.polygon.omswallet.models.WalletImportCipherSuite, java.lang.String); + public static technology.polygon.omswallet.models.WalletImportRecipientKey copy$default(technology.polygon.omswallet.models.WalletImportRecipientKey, java.lang.String, technology.polygon.omswallet.models.WalletImportCipherSuite, java.lang.String, int, java.lang.Object); public java.lang.String toString(); public int hashCode(); public boolean equals(java.lang.Object); } +Compiled from "OMSWalletModels.kt" +public final class technology.polygon.omswallet.models.WalletKeyOrigin extends java.lang.Enum { + public static final technology.polygon.omswallet.models.WalletKeyOrigin Enclave; + public static final technology.polygon.omswallet.models.WalletKeyOrigin Imported; + public static final technology.polygon.omswallet.models.WalletKeyOrigin UNKNOWN_DEFAULT; + public final java.lang.String getWireValue(); + public static technology.polygon.omswallet.models.WalletKeyOrigin[] values(); + public static technology.polygon.omswallet.models.WalletKeyOrigin valueOf(java.lang.String); + public static kotlin.enums.EnumEntries getEntries(); +} + Compiled from "OMSWalletModels.kt" public final class technology.polygon.omswallet.models.WalletType extends java.lang.Enum { public static final technology.polygon.omswallet.models.WalletType Ethereum; + public static final technology.polygon.omswallet.models.WalletType Solana; public static final technology.polygon.omswallet.models.WalletType UNKNOWN_DEFAULT; public final java.lang.String getWireValue(); public static technology.polygon.omswallet.models.WalletType[] values(); @@ -1021,9 +1482,11 @@ public final class technology.polygon.omswallet.network.OMSWalletEnvironment { public static final java.lang.String accessKeyHeaderName; public static final java.lang.String walletSignatureHeaderName; public static final java.lang.String walletSignatureHeaderPrefix; - public technology.polygon.omswallet.network.OMSWalletEnvironment(java.lang.String, java.lang.String); + public technology.polygon.omswallet.network.OMSWalletEnvironment(java.lang.String, java.lang.String, java.lang.String); + public technology.polygon.omswallet.network.OMSWalletEnvironment(java.lang.String, java.lang.String, java.lang.String, int, kotlin.jvm.internal.DefaultConstructorMarker); public final java.lang.String getWalletApiUrl(); public final java.lang.String getIndexerGatewayUrl(); + public final java.lang.String getSolanaIndexerGatewayUrl(); public final java.lang.String walletApiBaseUrl$oms_wallet_kotlin_sdk(); public boolean equals(java.lang.Object); public int hashCode(); @@ -1055,13 +1518,15 @@ public final class technology.polygon.omswallet.network.OMSWalletHttpClient$Comp Compiled from "OMSWalletHttpClient.kt" public final class technology.polygon.omswallet.network.OMSWalletHttpResponse { - public technology.polygon.omswallet.network.OMSWalletHttpResponse(int, java.lang.String); + public technology.polygon.omswallet.network.OMSWalletHttpResponse(int, java.lang.String, java.util.Map); public final int getStatusCode(); public final java.lang.String getBody(); + public final java.util.Map getHeaders(); public final int component1(); public final java.lang.String component2(); - public final technology.polygon.omswallet.network.OMSWalletHttpResponse copy(int, java.lang.String); - public static technology.polygon.omswallet.network.OMSWalletHttpResponse copy$default(technology.polygon.omswallet.network.OMSWalletHttpResponse, int, java.lang.String, int, java.lang.Object); + public final java.util.Map component3(); + public final technology.polygon.omswallet.network.OMSWalletHttpResponse copy(int, java.lang.String, java.util.Map); + public static technology.polygon.omswallet.network.OMSWalletHttpResponse copy$default(technology.polygon.omswallet.network.OMSWalletHttpResponse, int, java.lang.String, java.util.Map, int, java.lang.Object); public java.lang.String toString(); public int hashCode(); public boolean equals(java.lang.Object); @@ -1111,8 +1576,8 @@ public final class technology.polygon.omswallet.session.OMSWalletSession { public static boolean clear$default(technology.polygon.omswallet.session.OMSWalletSession, java.lang.Long, int, java.lang.Object); public final long replaceForPendingAuth(java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.Long); public static long replaceForPendingAuth$default(technology.polygon.omswallet.session.OMSWalletSession, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.Long, int, java.lang.Object); - public final kotlin.Pair markAuthVerified(java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, java.lang.Long); - public static kotlin.Pair markAuthVerified$default(technology.polygon.omswallet.session.OMSWalletSession, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, java.lang.Long, int, java.lang.Object); + public final kotlin.Pair markAuthVerified(java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, technology.polygon.omswallet.models.WalletType, java.lang.Long); + public static kotlin.Pair markAuthVerified$default(technology.polygon.omswallet.session.OMSWalletSession, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, technology.polygon.omswallet.models.WalletType, java.lang.Long, int, java.lang.Object); public final long selectWallet(java.lang.String, java.lang.String, java.lang.Long); public static long selectWallet$default(technology.polygon.omswallet.session.OMSWalletSession, java.lang.String, java.lang.String, java.lang.Long, int, java.lang.Object); public final long selectWalletForPendingSelection(long, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, java.lang.String); @@ -1148,20 +1613,22 @@ public final class technology.polygon.omswallet.session.OMSWalletSession$Session Compiled from "OMSWalletSession.kt" public final class technology.polygon.omswallet.session.OMSWalletSession$SessionState$AwaitingWalletSelection implements technology.polygon.omswallet.session.OMSWalletSession$SessionState { - public technology.polygon.omswallet.session.OMSWalletSession$SessionState$AwaitingWalletSelection(java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, java.lang.Long); + public technology.polygon.omswallet.session.OMSWalletSession$SessionState$AwaitingWalletSelection(java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, java.lang.Long, technology.polygon.omswallet.models.WalletType); public final java.lang.String getSignerAddress(); public final technology.polygon.omswallet.wallet.WalletSigningAlgorithm getSignerKeyType(); public final java.lang.String getExpiresAt(); public final technology.polygon.omswallet.OMSWalletSessionAuth getAuth(); public final java.lang.Long getPendingWalletSelectionId(); + public final technology.polygon.omswallet.models.WalletType getWalletType(); public technology.polygon.omswallet.session.OMSWalletSessionSnapshot snapshot(); public final java.lang.String component1(); public final technology.polygon.omswallet.wallet.WalletSigningAlgorithm component2(); public final java.lang.String component3(); public final technology.polygon.omswallet.OMSWalletSessionAuth component4(); public final java.lang.Long component5(); - public final technology.polygon.omswallet.session.OMSWalletSession$SessionState$AwaitingWalletSelection copy(java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, java.lang.Long); - public static technology.polygon.omswallet.session.OMSWalletSession$SessionState$AwaitingWalletSelection copy$default(technology.polygon.omswallet.session.OMSWalletSession$SessionState$AwaitingWalletSelection, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, java.lang.Long, int, java.lang.Object); + public final technology.polygon.omswallet.models.WalletType component6(); + public final technology.polygon.omswallet.session.OMSWalletSession$SessionState$AwaitingWalletSelection copy(java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, java.lang.Long, technology.polygon.omswallet.models.WalletType); + public static technology.polygon.omswallet.session.OMSWalletSession$SessionState$AwaitingWalletSelection copy$default(technology.polygon.omswallet.session.OMSWalletSession$SessionState$AwaitingWalletSelection, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, java.lang.Long, technology.polygon.omswallet.models.WalletType, int, java.lang.Object); public java.lang.String toString(); public int hashCode(); public boolean equals(java.lang.Object); @@ -1197,8 +1664,8 @@ public final class technology.polygon.omswallet.session.OMSWalletSession$Session Compiled from "OMSWalletSession.kt" public final class technology.polygon.omswallet.session.OMSWalletSessionSnapshot { - public technology.polygon.omswallet.session.OMSWalletSessionSnapshot(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth); - public technology.polygon.omswallet.session.OMSWalletSessionSnapshot(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, int, kotlin.jvm.internal.DefaultConstructorMarker); + public technology.polygon.omswallet.session.OMSWalletSessionSnapshot(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, java.lang.Long, technology.polygon.omswallet.models.WalletType); + public technology.polygon.omswallet.session.OMSWalletSessionSnapshot(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, java.lang.Long, technology.polygon.omswallet.models.WalletType, int, kotlin.jvm.internal.DefaultConstructorMarker); public final java.lang.String getChallenge(); public final java.lang.String getVerifier(); public final java.lang.String getWalletId(); @@ -1207,6 +1674,8 @@ public final class technology.polygon.omswallet.session.OMSWalletSessionSnapshot public final technology.polygon.omswallet.wallet.WalletSigningAlgorithm getSignerKeyType(); public final java.lang.String getExpiresAt(); public final technology.polygon.omswallet.OMSWalletSessionAuth getAuth(); + public final java.lang.Long getPendingWalletSelectionId(); + public final technology.polygon.omswallet.models.WalletType getPendingWalletType(); public final java.lang.String component1(); public final java.lang.String component2(); public final java.lang.String component3(); @@ -1215,8 +1684,10 @@ public final class technology.polygon.omswallet.session.OMSWalletSessionSnapshot public final technology.polygon.omswallet.wallet.WalletSigningAlgorithm component6(); public final java.lang.String component7(); public final technology.polygon.omswallet.OMSWalletSessionAuth component8(); - public final technology.polygon.omswallet.session.OMSWalletSessionSnapshot copy(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth); - public static technology.polygon.omswallet.session.OMSWalletSessionSnapshot copy$default(technology.polygon.omswallet.session.OMSWalletSessionSnapshot, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, int, java.lang.Object); + public final java.lang.Long component9(); + public final technology.polygon.omswallet.models.WalletType component10(); + public final technology.polygon.omswallet.session.OMSWalletSessionSnapshot copy(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, java.lang.Long, technology.polygon.omswallet.models.WalletType); + public static technology.polygon.omswallet.session.OMSWalletSessionSnapshot copy$default(technology.polygon.omswallet.session.OMSWalletSessionSnapshot, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, technology.polygon.omswallet.OMSWalletSessionAuth, java.lang.Long, technology.polygon.omswallet.models.WalletType, int, java.lang.Object); public java.lang.String toString(); public int hashCode(); public boolean equals(java.lang.Object); @@ -1357,23 +1828,30 @@ public final class technology.polygon.omswallet.wallet.AndroidKeystoreP256Creden public technology.polygon.omswallet.wallet.AndroidKeystoreP256CredentialSigner$Companion(kotlin.jvm.internal.DefaultConstructorMarker); } +Compiled from "AttestationVerifier.kt" +public final class technology.polygon.omswallet.wallet.AttestationVerifier { + public static final technology.polygon.omswallet.wallet.AttestationVerifier INSTANCE; + public final void verify(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.util.Set, long); + public static void verify$default(technology.polygon.omswallet.wallet.AttestationVerifier, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.util.Set, long, int, java.lang.Object); +} + Compiled from "WalletAuthResult.kt" public interface technology.polygon.omswallet.wallet.CompleteAuthResult { } Compiled from "WalletAuthResult.kt" public final class technology.polygon.omswallet.wallet.CompleteAuthResult$WalletSelected implements technology.polygon.omswallet.wallet.CompleteAuthResult { - public technology.polygon.omswallet.wallet.CompleteAuthResult$WalletSelected(java.lang.String, technology.polygon.omswallet.models.Wallet, java.util.List, technology.polygon.omswallet.models.CredentialInfo); + public technology.polygon.omswallet.wallet.CompleteAuthResult$WalletSelected(java.lang.String, technology.polygon.omswallet.models.Wallet, java.util.List, technology.polygon.omswallet.models.WalletCredential); public final java.lang.String getWalletAddress(); public final technology.polygon.omswallet.models.Wallet getWallet(); public final java.util.List getWallets(); - public final technology.polygon.omswallet.models.CredentialInfo getCredential(); + public final technology.polygon.omswallet.models.WalletCredential getCredential(); public final java.lang.String component1(); public final technology.polygon.omswallet.models.Wallet component2(); public final java.util.List component3(); - public final technology.polygon.omswallet.models.CredentialInfo component4(); - public final technology.polygon.omswallet.wallet.CompleteAuthResult$WalletSelected copy(java.lang.String, technology.polygon.omswallet.models.Wallet, java.util.List, technology.polygon.omswallet.models.CredentialInfo); - public static technology.polygon.omswallet.wallet.CompleteAuthResult$WalletSelected copy$default(technology.polygon.omswallet.wallet.CompleteAuthResult$WalletSelected, java.lang.String, technology.polygon.omswallet.models.Wallet, java.util.List, technology.polygon.omswallet.models.CredentialInfo, int, java.lang.Object); + public final technology.polygon.omswallet.models.WalletCredential component4(); + public final technology.polygon.omswallet.wallet.CompleteAuthResult$WalletSelected copy(java.lang.String, technology.polygon.omswallet.models.Wallet, java.util.List, technology.polygon.omswallet.models.WalletCredential); + public static technology.polygon.omswallet.wallet.CompleteAuthResult$WalletSelected copy$default(technology.polygon.omswallet.wallet.CompleteAuthResult$WalletSelected, java.lang.String, technology.polygon.omswallet.models.Wallet, java.util.List, technology.polygon.omswallet.models.WalletCredential, int, java.lang.Object); public java.lang.String toString(); public int hashCode(); public boolean equals(java.lang.Object); @@ -1651,10 +2129,10 @@ public final class technology.polygon.omswallet.wallet.PendingOidcRedirectAuth$C Compiled from "WalletAuthResult.kt" public final class technology.polygon.omswallet.wallet.PendingWalletSelection { - public technology.polygon.omswallet.wallet.PendingWalletSelection(technology.polygon.omswallet.models.WalletType, java.util.List, technology.polygon.omswallet.models.CredentialInfo, kotlin.jvm.functions.Function2, ? extends java.lang.Object>, kotlin.jvm.functions.Function2, ? extends java.lang.Object>); + public technology.polygon.omswallet.wallet.PendingWalletSelection(technology.polygon.omswallet.models.WalletType, java.util.List, technology.polygon.omswallet.models.WalletCredential, kotlin.jvm.functions.Function2, ? extends java.lang.Object>, kotlin.jvm.functions.Function2, ? extends java.lang.Object>); public final technology.polygon.omswallet.models.WalletType getWalletType(); public final java.util.List getWallets(); - public final technology.polygon.omswallet.models.CredentialInfo getCredential(); + public final technology.polygon.omswallet.models.WalletCredential getCredential(); public final java.lang.Object selectWallet(java.lang.String, kotlin.coroutines.Continuation); public final java.lang.Object createAndSelectWallet(java.lang.String, kotlin.coroutines.Continuation); public static java.lang.Object createAndSelectWallet$default(technology.polygon.omswallet.wallet.PendingWalletSelection, java.lang.String, kotlin.coroutines.Continuation, int, java.lang.Object); @@ -1707,6 +2185,12 @@ public final class technology.polygon.omswallet.wallet.WaasWalletGateway$WhenMap public static final int[] $EnumSwitchMapping$1; public static final int[] $EnumSwitchMapping$2; public static final int[] $EnumSwitchMapping$3; + public static final int[] $EnumSwitchMapping$4; + public static final int[] $EnumSwitchMapping$5; + public static final int[] $EnumSwitchMapping$6; + public static final int[] $EnumSwitchMapping$7; + public static final int[] $EnumSwitchMapping$8; + public static final int[] $EnumSwitchMapping$9; } Compiled from "WalletAuthChallenge.kt" @@ -1744,28 +2228,43 @@ public final class technology.polygon.omswallet.wallet.WalletClient { public final java.lang.Object useWallet(java.lang.String, kotlin.coroutines.Continuation); public final java.lang.Object createWallet(technology.polygon.omswallet.models.WalletType, java.lang.String, kotlin.coroutines.Continuation); public static java.lang.Object createWallet$default(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.models.WalletType, java.lang.String, kotlin.coroutines.Continuation, int, java.lang.Object); + public final java.lang.Object importWallet(technology.polygon.omswallet.models.WalletImportPrivateKey, java.lang.String, kotlin.coroutines.Continuation); + public static java.lang.Object importWallet$default(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.models.WalletImportPrivateKey, java.lang.String, kotlin.coroutines.Continuation, int, java.lang.Object); + public final java.lang.Object getWalletImportRecipientKey(technology.polygon.omswallet.models.WalletImportCipherSuite, kotlin.coroutines.Continuation); + public final java.lang.Object importEncryptedWallet(technology.polygon.omswallet.models.WalletType, technology.polygon.omswallet.models.EncryptedWalletImportKeyMaterial, java.lang.String, kotlin.coroutines.Continuation); + public static java.lang.Object importEncryptedWallet$default(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.models.WalletType, technology.polygon.omswallet.models.EncryptedWalletImportKeyMaterial, java.lang.String, kotlin.coroutines.Continuation, int, java.lang.Object); public final java.lang.Object listWallets(kotlin.coroutines.Continuation>); public final java.lang.Object signMessage(technology.polygon.omswallet.Network, java.lang.String, kotlin.coroutines.Continuation); + public final java.lang.Object signSolanaMessage(java.lang.String, kotlin.coroutines.Continuation); public final java.lang.Object signTypedData(technology.polygon.omswallet.Network, kotlinx.serialization.json.JsonElement, kotlin.coroutines.Continuation); public final java.lang.Object isValidMessageSignature(technology.polygon.omswallet.Network, java.lang.String, java.lang.String, kotlin.coroutines.Continuation); + public final java.lang.Object isValidSolanaMessageSignature(java.lang.String, java.lang.String, kotlin.coroutines.Continuation); public final java.lang.Object isValidTypedDataSignature(technology.polygon.omswallet.Network, kotlinx.serialization.json.JsonElement, java.lang.String, kotlin.coroutines.Continuation); public final java.lang.Object sendTransaction(technology.polygon.omswallet.Network, java.lang.String, java.math.BigInteger, boolean, technology.polygon.omswallet.models.TransactionStatusPollingOptions, technology.polygon.omswallet.models.FeeOptionSelector, kotlin.coroutines.Continuation); public static java.lang.Object sendTransaction$default(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.Network, java.lang.String, java.math.BigInteger, boolean, technology.polygon.omswallet.models.TransactionStatusPollingOptions, technology.polygon.omswallet.models.FeeOptionSelector, kotlin.coroutines.Continuation, int, java.lang.Object); public final java.lang.Object sendTransaction(technology.polygon.omswallet.Network, technology.polygon.omswallet.models.SendTransactionRequest, boolean, technology.polygon.omswallet.models.TransactionStatusPollingOptions, technology.polygon.omswallet.models.FeeOptionSelector, kotlin.coroutines.Continuation); public static java.lang.Object sendTransaction$default(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.Network, technology.polygon.omswallet.models.SendTransactionRequest, boolean, technology.polygon.omswallet.models.TransactionStatusPollingOptions, technology.polygon.omswallet.models.FeeOptionSelector, kotlin.coroutines.Continuation, int, java.lang.Object); + public final java.lang.Object sendSolanaTransfer(technology.polygon.omswallet.SolanaNetwork, java.lang.String, java.lang.String, java.math.BigInteger, technology.polygon.omswallet.models.TransactionMode, boolean, technology.polygon.omswallet.models.TransactionStatusPollingOptions, technology.polygon.omswallet.models.FeeOptionSelector, kotlin.coroutines.Continuation); + public static java.lang.Object sendSolanaTransfer$default(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.SolanaNetwork, java.lang.String, java.lang.String, java.math.BigInteger, technology.polygon.omswallet.models.TransactionMode, boolean, technology.polygon.omswallet.models.TransactionStatusPollingOptions, technology.polygon.omswallet.models.FeeOptionSelector, kotlin.coroutines.Continuation, int, java.lang.Object); public final java.lang.Object callContract(technology.polygon.omswallet.Network, java.lang.String, java.lang.String, java.util.List, technology.polygon.omswallet.models.TransactionMode, boolean, technology.polygon.omswallet.models.TransactionStatusPollingOptions, technology.polygon.omswallet.models.FeeOptionSelector, kotlin.coroutines.Continuation); public static java.lang.Object callContract$default(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.Network, java.lang.String, java.lang.String, java.util.List, technology.polygon.omswallet.models.TransactionMode, boolean, technology.polygon.omswallet.models.TransactionStatusPollingOptions, technology.polygon.omswallet.models.FeeOptionSelector, kotlin.coroutines.Continuation, int, java.lang.Object); public final java.lang.Object getTransactionStatus(java.lang.String, kotlin.coroutines.Continuation); - public final java.lang.Object listAccess-aPkLuA0(kotlin.UInt, kotlin.coroutines.Continuation>); - public static java.lang.Object listAccess-aPkLuA0$default(technology.polygon.omswallet.wallet.WalletClient, kotlin.UInt, kotlin.coroutines.Continuation, int, java.lang.Object); - public final kotlinx.coroutines.flow.Flow listAccessPages-ExVfyTY(kotlin.UInt); - public static kotlinx.coroutines.flow.Flow listAccessPages-ExVfyTY$default(technology.polygon.omswallet.wallet.WalletClient, kotlin.UInt, int, java.lang.Object); - public final java.lang.Object listAccessPage-K5VMiEY(kotlin.UInt, java.lang.String, kotlin.coroutines.Continuation); - public static java.lang.Object listAccessPage-K5VMiEY$default(technology.polygon.omswallet.wallet.WalletClient, kotlin.UInt, java.lang.String, kotlin.coroutines.Continuation, int, java.lang.Object); + public final java.lang.Object inspectRemoteCredential(java.lang.String, kotlin.coroutines.Continuation); + public final java.lang.Object authorizeRemoteAccess(java.lang.String, technology.polygon.omswallet.Network, java.util.List, java.lang.String, java.lang.String, kotlin.coroutines.Continuation); + public static java.lang.Object authorizeRemoteAccess$default(technology.polygon.omswallet.wallet.WalletClient, java.lang.String, technology.polygon.omswallet.Network, java.util.List, java.lang.String, java.lang.String, kotlin.coroutines.Continuation, int, java.lang.Object); + public final java.lang.Object listAccess-K5VMiEY(kotlin.UInt, technology.polygon.omswallet.models.AccessGrantType, kotlin.coroutines.Continuation>); + public static java.lang.Object listAccess-K5VMiEY$default(technology.polygon.omswallet.wallet.WalletClient, kotlin.UInt, technology.polygon.omswallet.models.AccessGrantType, kotlin.coroutines.Continuation, int, java.lang.Object); + public final kotlinx.coroutines.flow.Flow listAccessPages-aPkLuA0(kotlin.UInt, technology.polygon.omswallet.models.AccessGrantType); + public static kotlinx.coroutines.flow.Flow listAccessPages-aPkLuA0$default(technology.polygon.omswallet.wallet.WalletClient, kotlin.UInt, technology.polygon.omswallet.models.AccessGrantType, int, java.lang.Object); + public final java.lang.Object listAccessPage-li-XMhY(kotlin.UInt, java.lang.String, technology.polygon.omswallet.models.AccessGrantType, kotlin.coroutines.Continuation); + public static java.lang.Object listAccessPage-li-XMhY$default(technology.polygon.omswallet.wallet.WalletClient, kotlin.UInt, java.lang.String, technology.polygon.omswallet.models.AccessGrantType, kotlin.coroutines.Continuation, int, java.lang.Object); + public final java.lang.Object getRemoteAccessSession(java.lang.String, kotlin.coroutines.Continuation); + public final java.lang.Object getRemoteAccessSessionUsage(java.lang.String, technology.polygon.omswallet.Network, kotlin.coroutines.Continuation>); public final java.lang.Object getIdToken-K5VMiEY(kotlin.UInt, java.util.Map, kotlin.coroutines.Continuation); public static java.lang.Object getIdToken-K5VMiEY$default(technology.polygon.omswallet.wallet.WalletClient, kotlin.UInt, java.util.Map, kotlin.coroutines.Continuation, int, java.lang.Object); - public final java.lang.Object revokeAccess(java.lang.String, kotlin.coroutines.Continuation); - public technology.polygon.omswallet.wallet.WalletClient(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, technology.polygon.omswallet.network.OMSWalletHttpClient, technology.polygon.omswallet.wallet.WalletScopeRuntime, kotlin.jvm.functions.Function0, long, int, long, long, kotlin.jvm.functions.Function2, kotlin.jvm.internal.DefaultConstructorMarker); + public final java.lang.Object revokeAccess(java.lang.String, java.lang.String, kotlin.coroutines.Continuation); + public static java.lang.Object revokeAccess$default(technology.polygon.omswallet.wallet.WalletClient, java.lang.String, java.lang.String, kotlin.coroutines.Continuation, int, java.lang.Object); + public technology.polygon.omswallet.wallet.WalletClient(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, technology.polygon.omswallet.network.OMSWalletHttpClient, technology.polygon.omswallet.wallet.WalletScopeRuntime, kotlin.jvm.functions.Function0, long, int, long, long, kotlin.jvm.functions.Function2, technology.polygon.omswallet.WalletImportConfiguration, kotlin.jvm.internal.DefaultConstructorMarker); public static final int access$requireWaasSessionLifetimeSeconds-OGnWXxg(technology.polygon.omswallet.wallet.WalletClient, long); public static final technology.polygon.omswallet.wallet.WaasWalletGateway access$getGateway$p(technology.polygon.omswallet.wallet.WalletClient); public static final technology.polygon.omswallet.wallet.WalletScopeRuntime access$getRuntime$p(technology.polygon.omswallet.wallet.WalletClient); @@ -1790,15 +2289,19 @@ public final class technology.polygon.omswallet.wallet.WalletClient { public static final java.lang.Object access$completeEmailSignIn-Yuhug_o(technology.polygon.omswallet.wallet.WalletClient, java.lang.String, int, long, kotlin.coroutines.Continuation); public static final java.lang.Object access$completeOidcIdTokenSignIn-Yuhug_o(technology.polygon.omswallet.wallet.WalletClient, java.lang.String, int, long, kotlin.coroutines.Continuation); public static final java.lang.Object access$useWalletForCurrentSession(technology.polygon.omswallet.wallet.WalletClient, java.lang.String, long, technology.polygon.omswallet.wallet.PendingOidcRedirectAuth, kotlin.jvm.functions.Function1, kotlin.coroutines.Continuation); + public static final technology.polygon.omswallet.wallet.WalletImportActivationContext access$walletImportActivationContext(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.models.WalletType); + public static final technology.polygon.omswallet.wallet.WalletSelectionResult access$activateImportedWallet(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.models.Wallet, technology.polygon.omswallet.wallet.WalletImportActivationContext); + public static final long access$requireWalletSelectionOrActiveSession(technology.polygon.omswallet.wallet.WalletClient, java.lang.Long); public static final java.lang.Object access$createWalletForCurrentSession(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.models.WalletType, java.lang.String, long, technology.polygon.omswallet.wallet.PendingOidcRedirectAuth, kotlin.jvm.functions.Function1, kotlin.coroutines.Continuation); public static final java.lang.Object access$useWalletForPendingSelection(technology.polygon.omswallet.wallet.WalletClient, long, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, java.lang.String, kotlin.coroutines.Continuation); public static final java.lang.Object access$createWalletForPendingSelection(technology.polygon.omswallet.wallet.WalletClient, long, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, technology.polygon.omswallet.models.WalletType, java.lang.String, kotlin.coroutines.Continuation); public static final java.lang.Object access$requestUseWallet(technology.polygon.omswallet.wallet.WalletClient, java.lang.String, long, kotlin.coroutines.Continuation); public static final java.lang.Object access$requestCreateWallet(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.models.WalletType, java.lang.String, long, kotlin.coroutines.Continuation); public static final java.lang.Object access$walletsFromAuthResponse(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.wallet.WalletAuthCompletion, long, kotlin.coroutines.Continuation); + public static final boolean access$isEthereumAddress(technology.polygon.omswallet.wallet.WalletClient, java.lang.String); public static final technology.polygon.omswallet.wallet.ActiveWalletSession access$requireActiveWalletSession(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.OMSWalletOperation, boolean); public static final java.lang.Object access$executePreparedTransaction(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.Network, java.lang.String, technology.polygon.omswallet.wallet.PreparedWalletTransaction, long, technology.polygon.omswallet.models.FeeOptionSelector, boolean, technology.polygon.omswallet.models.TransactionStatusPollingOptions, kotlin.coroutines.Continuation); - public static final java.lang.Object access$requestListAccessPage-li-XMhY(technology.polygon.omswallet.wallet.WalletClient, kotlin.UInt, java.lang.String, technology.polygon.omswallet.wallet.ActiveWalletSession, kotlin.coroutines.Continuation); + public static final java.lang.Object access$requestListAccessPage-zURRx2s(technology.polygon.omswallet.wallet.WalletClient, kotlin.UInt, java.lang.String, technology.polygon.omswallet.models.AccessGrantType, technology.polygon.omswallet.wallet.ActiveWalletSession, kotlin.coroutines.Continuation); public static final java.lang.Object access$enrichFeeOptionsWithBalances(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.Network, java.lang.String, java.util.List, kotlin.coroutines.Continuation); public static final java.lang.Object access$waitForTransactionStatus(technology.polygon.omswallet.wallet.WalletClient, java.lang.String, technology.polygon.omswallet.models.TransactionStatus, technology.polygon.omswallet.models.TransactionStatusPollingOptions, long, kotlin.coroutines.Continuation); public static final java.lang.String access$authorizeSignedRequest(technology.polygon.omswallet.wallet.WalletClient, long, boolean, java.lang.String, java.lang.String); @@ -1806,8 +2309,8 @@ public final class technology.polygon.omswallet.wallet.WalletClient { Compiled from "WalletClient.kt" public final class technology.polygon.omswallet.wallet.WalletClient$Companion { - public final technology.polygon.omswallet.wallet.WalletClient create$oms_wallet_kotlin_sdk(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, technology.polygon.omswallet.network.OMSWalletHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, kotlin.jvm.functions.Function0, technology.polygon.omswallet.wallet.CredentialSigner, long, int, long, long, kotlin.jvm.functions.Function2, technology.polygon.omswallet.wallet.SessionExpiryScheduler, technology.polygon.omswallet.wallet.SessionExpiryDispatcher, kotlin.jvm.functions.Function0, java.lang.String); - public static technology.polygon.omswallet.wallet.WalletClient create$oms_wallet_kotlin_sdk$default(technology.polygon.omswallet.wallet.WalletClient$Companion, java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, technology.polygon.omswallet.network.OMSWalletHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, kotlin.jvm.functions.Function0, technology.polygon.omswallet.wallet.CredentialSigner, long, int, long, long, kotlin.jvm.functions.Function2, technology.polygon.omswallet.wallet.SessionExpiryScheduler, technology.polygon.omswallet.wallet.SessionExpiryDispatcher, kotlin.jvm.functions.Function0, java.lang.String, int, java.lang.Object); + public final technology.polygon.omswallet.wallet.WalletClient create$oms_wallet_kotlin_sdk(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, technology.polygon.omswallet.network.OMSWalletHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, kotlin.jvm.functions.Function0, technology.polygon.omswallet.wallet.CredentialSigner, long, int, long, long, kotlin.jvm.functions.Function2, technology.polygon.omswallet.wallet.SessionExpiryScheduler, technology.polygon.omswallet.wallet.SessionExpiryDispatcher, kotlin.jvm.functions.Function0, java.lang.String, technology.polygon.omswallet.WalletImportConfiguration); + public static technology.polygon.omswallet.wallet.WalletClient create$oms_wallet_kotlin_sdk$default(technology.polygon.omswallet.wallet.WalletClient$Companion, java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, technology.polygon.omswallet.network.OMSWalletHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, kotlin.jvm.functions.Function0, technology.polygon.omswallet.wallet.CredentialSigner, long, int, long, long, kotlin.jvm.functions.Function2, technology.polygon.omswallet.wallet.SessionExpiryScheduler, technology.polygon.omswallet.wallet.SessionExpiryDispatcher, kotlin.jvm.functions.Function0, java.lang.String, technology.polygon.omswallet.WalletImportConfiguration, int, java.lang.Object); public technology.polygon.omswallet.wallet.WalletClient$Companion(kotlin.jvm.internal.DefaultConstructorMarker); } @@ -1831,6 +2334,53 @@ public final class technology.polygon.omswallet.wallet.WalletClientKt { public static final technology.polygon.omswallet.OMSWalletSessionExpiredEvent access$toSessionExpiredEvent(technology.polygon.omswallet.session.OMSWalletSessionSnapshot); } +Compiled from "WalletClient.kt" +public final class technology.polygon.omswallet.wallet.WalletImportActivationContext$Active implements technology.polygon.omswallet.wallet.WalletImportActivationContext { + public technology.polygon.omswallet.wallet.WalletImportActivationContext$Active(java.lang.String, long); + public final java.lang.String getWalletId(); + public long getRevision(); + public final java.lang.String component1(); + public final long component2(); + public final technology.polygon.omswallet.wallet.WalletImportActivationContext$Active copy(java.lang.String, long); + public static technology.polygon.omswallet.wallet.WalletImportActivationContext$Active copy$default(technology.polygon.omswallet.wallet.WalletImportActivationContext$Active, java.lang.String, long, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "WalletClient.kt" +public final class technology.polygon.omswallet.wallet.WalletImportActivationContext$Pending implements technology.polygon.omswallet.wallet.WalletImportActivationContext { + public technology.polygon.omswallet.wallet.WalletImportActivationContext$Pending(long, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, long); + public final long getId(); + public final java.lang.String getSignerAddress(); + public final technology.polygon.omswallet.wallet.WalletSigningAlgorithm getSignerKeyType(); + public long getRevision(); + public final long component1(); + public final java.lang.String component2(); + public final technology.polygon.omswallet.wallet.WalletSigningAlgorithm component3(); + public final long component4(); + public final technology.polygon.omswallet.wallet.WalletImportActivationContext$Pending copy(long, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, long); + public static technology.polygon.omswallet.wallet.WalletImportActivationContext$Pending copy$default(technology.polygon.omswallet.wallet.WalletImportActivationContext$Pending, long, java.lang.String, technology.polygon.omswallet.wallet.WalletSigningAlgorithm, long, int, java.lang.Object); + public java.lang.String toString(); + public int hashCode(); + public boolean equals(java.lang.Object); +} + +Compiled from "WalletImportCrypto.kt" +public final class technology.polygon.omswallet.wallet.WalletImportBase64 { + public static final technology.polygon.omswallet.wallet.WalletImportBase64 INSTANCE; + public final java.lang.String encode(byte[]); + public final byte[] decodeCanonical(java.lang.String, java.lang.String); +} + +Compiled from "WalletImportCrypto.kt" +public final class technology.polygon.omswallet.wallet.WalletImportCrypto { + public static final technology.polygon.omswallet.wallet.WalletImportCrypto INSTANCE; + public final byte[] plaintext(technology.polygon.omswallet.models.WalletImportPrivateKey); + public final void validateReference(java.lang.String); + public final kotlin.Pair sealP256Aes256Gcm(byte[], byte[]); +} + Compiled from "WalletRequestSigner.kt" public final class technology.polygon.omswallet.wallet.WalletRequestSigner { public static final technology.polygon.omswallet.wallet.WalletRequestSigner INSTANCE; diff --git a/oms-wallet-kotlin-sdk/build.gradle.kts b/oms-wallet-kotlin-sdk/build.gradle.kts index b87fbf9..2485f44 100644 --- a/oms-wallet-kotlin-sdk/build.gradle.kts +++ b/oms-wallet-kotlin-sdk/build.gradle.kts @@ -61,6 +61,11 @@ android { } } +dependencies { + implementation(libs.bouncy.castle) + implementation(libs.cbor) +} + tasks.withType().configureEach { exclude("**/generated/**") doLast { diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/Network.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/Network.kt index 1a591fa..c5b9fa4 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/Network.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/Network.kt @@ -1,5 +1,17 @@ package technology.polygon.omswallet +enum class SolanaNetwork( + val wireValue: String, +) { + Devnet("solana:devnet"), + Mainnet("solana:mainnet"), +} + +object SolanaNetworks { + val DEVNET: SolanaNetwork = SolanaNetwork.Devnet + val MAINNET: SolanaNetwork = SolanaNetwork.Mainnet +} + /** * A network supported by the OMS Wallet SDK. * diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWallet.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWallet.kt index 7e6ad49..4aca7f4 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWallet.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWallet.kt @@ -32,6 +32,7 @@ class OMSWallet private constructor( oidcRedirectAuthStore: OidcRedirectAuthStore?, credentialSigner: CredentialSigner?, projectScopeKey: String?, + walletImport: WalletImportConfiguration?, ) { private val resolvedProjectId: String = projectId ?: parsePublishableKey(publishableKey).projectId private val resolvedEnvironment: OMSWalletEnvironment = @@ -50,6 +51,7 @@ class OMSWallet private constructor( oidcRedirectAuthStore = oidcRedirectAuthStore, credentialSigner = credentialSigner, projectScopeKey = projectScopeKey, + walletImport = walletImport, ) val indexer: IndexerClient = @@ -75,6 +77,7 @@ class OMSWallet private constructor( context: Context, publishableKey: String, okHttpClient: OkHttpClient = OkHttpClient(), + walletImport: WalletImportConfiguration? = null, ) : this( publishableKey = publishableKey, projectId = projectIdFromPublishableKey(publishableKey), @@ -98,6 +101,7 @@ class OMSWallet private constructor( nonceStoreName = scopedCredentialNonceStoreName(publishableKey), ), projectScopeKey = scopedSessionSuffix(publishableKey), + walletImport = walletImport, ) companion object { @@ -112,6 +116,7 @@ class OMSWallet private constructor( oidcRedirectAuthStore: OidcRedirectAuthStore? = null, credentialSigner: CredentialSigner? = null, projectScopeKey: String? = null, + walletImport: WalletImportConfiguration? = null, ): OMSWallet = OMSWallet( publishableKey = publishableKey, @@ -123,6 +128,7 @@ class OMSWallet private constructor( oidcRedirectAuthStore = oidcRedirectAuthStore, credentialSigner = credentialSigner, projectScopeKey = projectScopeKey, + walletImport = walletImport, ) private fun projectIdFromPublishableKey(publishableKey: String): String = parsePublishableKey(publishableKey).projectId diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWalletError.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWalletError.kt index 78bf074..bae0472 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWalletError.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWalletError.kt @@ -24,6 +24,7 @@ enum class OMSWalletErrorCode( TransactionStatusLookupFailed("OMS_TRANSACTION_STATUS_LOOKUP_FAILED"), ValidationError("OMS_VALIDATION_ERROR"), StorageError("OMS_STORAGE_ERROR"), + AttestationVerificationFailed("OMS_ATTESTATION_VERIFICATION_FAILED"), } /** @@ -36,24 +37,35 @@ enum class OMSWalletOperation( PendingWalletSelectionCreateAndSelectWallet("wallet.pendingWalletSelection.createAndSelectWallet"), PendingWalletSelectionSelectWallet("wallet.pendingWalletSelection.selectWallet"), IndexerGetBalances("indexer.getBalances"), + IndexerGetSolanaBalances("indexer.getSolanaBalances"), IndexerGetTransactionHistory("indexer.getTransactionHistory"), WalletCallContract("wallet.callContract"), + WalletAuthorizeRemoteAccess("wallet.authorizeRemoteAccess"), WalletCompleteEmailAuth("wallet.completeEmailAuth"), WalletCreateWallet("wallet.createWallet"), + WalletImportWallet("wallet.importWallet"), + WalletGetImportRecipientKey("wallet.getWalletImportRecipientKey"), + WalletImportEncryptedWallet("wallet.importEncryptedWallet"), WalletExecute("wallet.execute"), WalletGetIdToken("wallet.getIdToken"), + WalletGetRemoteAccessSession("wallet.getRemoteAccessSession"), + WalletGetRemoteAccessSessionUsage("wallet.getRemoteAccessSessionUsage"), WalletHandleOidcRedirectCallback("wallet.handleOidcRedirectCallback"), WalletGetTransactionStatus("wallet.getTransactionStatus"), WalletIsValidMessageSignature("wallet.isValidMessageSignature"), + WalletIsValidSolanaMessageSignature("wallet.isValidSolanaMessageSignature"), WalletIsValidTypedDataSignature("wallet.isValidTypedDataSignature"), + WalletInspectRemoteCredential("wallet.inspectRemoteCredential"), WalletListAccess("wallet.listAccess"), WalletListAccessPage("wallet.listAccessPage"), WalletListAccessPages("wallet.listAccessPages"), WalletListWallets("wallet.listWallets"), WalletRevokeAccess("wallet.revokeAccess"), WalletSendTransaction("wallet.sendTransaction"), + WalletSendSolanaTransfer("wallet.sendSolanaTransfer"), WalletSignInWithOidcIdToken("wallet.signInWithOidcIdToken"), WalletSignMessage("wallet.signMessage"), + WalletSignSolanaMessage("wallet.signSolanaMessage"), WalletSignOut("wallet.signOut"), WalletSignTypedData("wallet.signTypedData"), WalletStartEmailAuth("wallet.startEmailAuth"), @@ -159,6 +171,19 @@ class OMSWalletResponseException( cause = cause, ) +/** Thrown when a wallet-import response cannot be authenticated as an approved enclave. */ +class OMSWalletAttestationException( + operation: OMSWalletOperation? = null, + message: String, + cause: Throwable? = null, +) : OMSWalletException( + code = OMSWalletErrorCode.AttestationVerificationFailed, + operation = operation, + retryable = false, + message = message, + cause = cause, + ) + class OMSWalletTransactionException( code: OMSWalletErrorCode = OMSWalletErrorCode.TransactionStatusLookupFailed, operation: OMSWalletOperation? = null, @@ -444,6 +469,14 @@ private fun OMSWalletException.withOperation(operation: OMSWalletOperation): OMS cause = this, ) } + + is OMSWalletAttestationException -> { + OMSWalletAttestationException( + operation = operation, + message = message ?: operation.id, + cause = this, + ) + } } private fun WebRpcError.normalizedStatus(): Int? { diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/ParsedPublishableKey.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/ParsedPublishableKey.kt index 85585a7..43fd7f0 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/ParsedPublishableKey.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/ParsedPublishableKey.kt @@ -7,6 +7,7 @@ internal data class ParsedPublishableKey( val projectId: String, val walletApiUrl: String, val indexerGatewayUrl: String, + val solanaIndexerGatewayUrl: String = "${walletApiUrl.trimEnd('/')}/v1/SolanaIndexerGateway/", ) /** @@ -27,6 +28,7 @@ internal fun parsePublishableKey(publishableKey: String): ParsedPublishableKey { projectId = "prj_${keyParts[0]}", walletApiUrl = route.apiUrl, indexerGatewayUrl = "${route.apiUrl}/v1/IndexerGateway/", + solanaIndexerGatewayUrl = "${route.apiUrl}/v1/SolanaIndexerGateway/", ) } diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/WalletImportConfiguration.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/WalletImportConfiguration.kt new file mode 100644 index 0000000..0c3875f --- /dev/null +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/WalletImportConfiguration.kt @@ -0,0 +1,22 @@ +package technology.polygon.omswallet + +/** Trust policy used to verify attested wallet-import responses. */ +class WalletImportConfiguration( + trustedPcr0s: Collection, +) { + internal val trustedPcr0s: Set = + trustedPcr0s + .map { it.trim().lowercase().removePrefix("0x") } + .also { normalized -> + require( + normalized.isNotEmpty() && + normalized.all { value -> + value.length == 96 && + value.all { it in '0'..'9' || it in 'a'..'f' } && + value.any { it != '0' } + }, + ) { + "walletImport.trustedPcr0s must contain at least one nonzero 48-byte hex PCR0" + } + }.toSet() +} diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/indexer/IndexerClient.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/indexer/IndexerClient.kt index 58eadea..5294a7c 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/indexer/IndexerClient.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/indexer/IndexerClient.kt @@ -23,11 +23,18 @@ import technology.polygon.omswallet.OMSWalletRequestException import technology.polygon.omswallet.OMSWalletResponseException import technology.polygon.omswallet.OMSWalletUpstreamError import technology.polygon.omswallet.OMSWalletUpstreamService +import technology.polygon.omswallet.SolanaNetwork import technology.polygon.omswallet.models.ContractTokenBalance import technology.polygon.omswallet.models.ContractVerificationStatus import technology.polygon.omswallet.models.IndexerNetworkType import technology.polygon.omswallet.models.MetadataOptions import technology.polygon.omswallet.models.NativeTokenBalance +import technology.polygon.omswallet.models.SolanaBalance +import technology.polygon.omswallet.models.SolanaBalancesResult +import technology.polygon.omswallet.models.SolanaNetworkError +import technology.polygon.omswallet.models.SolanaTokenProgram +import technology.polygon.omswallet.models.SolanaVerificationSource +import technology.polygon.omswallet.models.SolanaVerificationStatus import technology.polygon.omswallet.models.TokenBalance import technology.polygon.omswallet.models.TokenBalancesPage import technology.polygon.omswallet.models.TokenBalancesPageRequest @@ -178,18 +185,57 @@ class IndexerClient private constructor( } } + /** Gets native SOL and fungible-token balances for [walletAddress]. */ + suspend fun getSolanaBalances( + walletAddress: String, + networks: List = listOf(SolanaNetwork.Mainnet, SolanaNetwork.Devnet), + includeMetadata: Boolean = true, + omitNativeBalances: Boolean? = null, + mintAddresses: List = emptyList(), + excludedMintAddresses: List = emptyList(), + ): SolanaBalancesResult { + val operation = OMSWalletOperation.IndexerGetSolanaBalances + val response = + postIndexerGatewayJson( + operation = operation, + baseUrl = environment.solanaIndexerGatewayUrl, + webRpcHeaderValue = solanaIndexerGatewayWebrpcHeaderValue, + path = "/GetTokenBalancesDetails", + body = + buildJsonObject { + putJsonArray("networks") { networks.forEach { add(it.wireValue) } } + putJsonObject("filter") { + putJsonArray("accountAddresses") { add(walletAddress) } + omitNativeBalances?.let { put("omitNativeBalances", it) } + putStringArrayIfNotEmpty("contractWhitelist", mintAddresses) + putStringArrayIfNotEmpty("contractBlacklist", excludedMintAddresses) + } + put("omitMetadata", includeMetadata == false) + }.toString(), + ) + return decodeIndexerResponse(response, operation) { root -> + SolanaBalancesResult( + status = response.statusCode, + balances = root.requiredObjectArray("balances").map { it.toSolanaBalance() }, + errors = root.requiredObjectArray("errors").map { it.toSolanaNetworkError() }, + ) + } + } + private suspend fun postIndexerGatewayJson( operation: OMSWalletOperation, + baseUrl: String = environment.indexerGatewayUrl, + webRpcHeaderValue: String = indexerGatewayWebrpcHeaderValue, path: String, body: String, ): OMSWalletHttpResponse { val response = try { transport.postJsonWithStatus( - baseUrl = environment.indexerGatewayUrl, + baseUrl = baseUrl, path = path, body = body, - headers = defaultGatewayHeaders(), + headers = defaultGatewayHeaders(webRpcHeaderValue), ) } catch (throwable: CancellationException) { throw throwable @@ -271,13 +317,109 @@ class IndexerClient private constructor( ) } - private fun defaultGatewayHeaders(): Map = + private fun defaultGatewayHeaders(webRpcHeaderValue: String): Map = mapOf( "Api-Key" to publishableKey, "Accept" to "application/json", - "Webrpc" to indexerGatewayWebrpcHeaderValue, + "Webrpc" to webRpcHeaderValue, ) + private fun JsonObject.toSolanaBalance(): SolanaBalance { + val network = requiredSolanaNetwork("network") + val accountAddress = requiredString("accountAddress") + val name = requiredString("name") + val symbol = requiredString("symbol") + val decimals = requiredInt("decimals") + val balance = requiredString("balance") + val formattedBalance = requiredString("formattedBalance") + val imageUrl = optionalNonEmptyString("imageUrl") + val metadataUri = optionalNonEmptyString("metadataUri") + val verificationStatus = requiredVerificationStatus("verificationStatus") + val verificationSource = requiredVerificationSource("verificationSource") + val priceUSD = optionalString("priceUSD") + val balanceUSD = optionalString("balanceUSD") + return when (requiredString("assetType")) { + "native" -> { + require(this["tokenProgram"] == null || this["tokenProgram"] === JsonNull) + require(this["mintAddress"] == null || this["mintAddress"] === JsonNull) + SolanaBalance.Native( + network, + accountAddress, + name, + symbol, + decimals, + balance, + formattedBalance, + imageUrl, + metadataUri, + verificationStatus, + verificationSource, + priceUSD, + balanceUSD, + ) + } + + "fungible-token" -> { + SolanaBalance.FungibleToken( + network, + accountAddress, + requiredTokenProgram("tokenProgram"), + requiredString("mintAddress"), + name, + symbol, + decimals, + balance, + formattedBalance, + imageUrl, + metadataUri, + verificationStatus, + verificationSource, + priceUSD, + balanceUSD, + ) + } + + else -> { + throw IllegalArgumentException("Invalid assetType") + } + } + } + + private fun JsonObject.toSolanaNetworkError(): SolanaNetworkError = + SolanaNetworkError(requiredSolanaNetwork("network"), requiredString("reason")) + + private fun JsonObject.requiredSolanaNetwork(name: String): SolanaNetwork = + when (requiredString(name)) { + SolanaNetwork.Mainnet.wireValue -> SolanaNetwork.Mainnet + SolanaNetwork.Devnet.wireValue -> SolanaNetwork.Devnet + else -> throw IllegalArgumentException("Invalid $name") + } + + private fun JsonObject.requiredTokenProgram(name: String): SolanaTokenProgram = + when (requiredString(name)) { + "spl-token" -> SolanaTokenProgram.SplToken + "token-2022" -> SolanaTokenProgram.Token2022 + else -> throw IllegalArgumentException("Invalid $name") + } + + private fun JsonObject.requiredVerificationStatus(name: String): SolanaVerificationStatus = + when (requiredString(name)) { + "verified" -> SolanaVerificationStatus.Verified + "unverified" -> SolanaVerificationStatus.Unverified + "unknown" -> SolanaVerificationStatus.Unknown + else -> throw IllegalArgumentException("Invalid $name") + } + + private fun JsonObject.requiredVerificationSource(name: String): SolanaVerificationSource = + when (requiredString(name)) { + "jupiter" -> SolanaVerificationSource.Jupiter + "solflare-utl" -> SolanaVerificationSource.SolflareUtl + "none" -> SolanaVerificationSource.None + else -> throw IllegalArgumentException("Invalid $name") + } + + private fun JsonObject.optionalNonEmptyString(name: String): String? = optionalString(name)?.takeIf(String::isNotEmpty) + private fun JsonObject.toTokenBalancesPage(): TokenBalancesPage = TokenBalancesPage( page = requiredInt("page"), @@ -574,5 +716,7 @@ class IndexerClient private constructor( private const val indexerGatewayWebrpcHeaderValue: String = "webrpc@v0.31.2;gen-typescript@v0.23.1;sequence-indexer@v0.4.0" + private const val solanaIndexerGatewayWebrpcHeaderValue: String = + "webrpc@v0.31.2;gen-kotlin@v0.3.2;solana-indexer-gateway@v1" } } diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/internal/generated/waas/WaasWalletClient.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/internal/generated/waas/WaasWalletClient.kt index f5ecbc0..67ee410 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/internal/generated/waas/WaasWalletClient.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/internal/generated/waas/WaasWalletClient.kt @@ -15,26 +15,27 @@ import kotlinx.serialization.json.JsonElement import kotlinx.coroutines.CancellationException import java.io.IOException -// waas v1-26.6.17-061733f 2279592e720c50a33130cd3fd935b3e2e74ff67d +// waas v1-26.8.24-262bd7ba 688ad6c684fc1ffa8aab10d476c7fb2dd60a5019 // -- // Code generated by webrpc-gen@v0.37.2 with github.com/webrpc/gen-kotlin@v0.3.2 generator. DO NOT EDIT. // -// webrpc-gen -schema=schema/waas.ridl -target=github.com/webrpc/gen-kotlin@v0.3.2 -client -packageName=io.sequence.waas -out=./clients/waas.gen.kt +// webrpc-gen -schema=schema/waas.ridl -target=github.com/webrpc/gen-kotlin@v0.3.2 -client -packageName=technology.polygon.omswallet.internal.generated.waas -out=./clients/waas.gen.kt // WebRPC description and code-gen version const val WEBRPC_VERSION = "v1" // Schema version of your RIDL schema -const val WEBRPC_SCHEMA_VERSION = "v1-26.6.17-061733f" +const val WEBRPC_SCHEMA_VERSION = "v1-26.8.24-262bd7ba" // Schema hash generated from your RIDL schema -const val WEBRPC_SCHEMA_HASH = "2279592e720c50a33130cd3fd935b3e2e74ff67d" +const val WEBRPC_SCHEMA_HASH = "688ad6c684fc1ffa8aab10d476c7fb2dd60a5019" // region Types @Serializable(with = WalletTypeSerializer::class) enum class WalletType(val wireValue: String) { Ethereum("ethereum"), + Solana("solana"), UNKNOWN_DEFAULT("UNKNOWN_DEFAULT"); companion object { @@ -65,6 +66,74 @@ object WalletTypeSerializer : KSerializer { } +@Serializable(with = NetworkFamilySerializer::class) +enum class NetworkFamily(val wireValue: String) { + EVM("evm"), + Solana("solana"), + UNKNOWN_DEFAULT("UNKNOWN_DEFAULT"); + + companion object { + fun fromWireValue(value: String): NetworkFamily { + return values().find { it.wireValue == value } ?: UNKNOWN_DEFAULT + } + } +} + +object NetworkFamilySerializer : KSerializer { + override val descriptor: SerialDescriptor = + String.serializer().descriptor + + override fun serialize(encoder: Encoder, value: NetworkFamily) { + encoder.encodeSerializableValue( + String.serializer(), + value.wireValue, + ) + } + + override fun deserialize(decoder: Decoder): NetworkFamily { + return NetworkFamily.fromWireValue( + decoder.decodeSerializableValue( + String.serializer(), + ), + ) + } +} + + +@Serializable(with = WalletImplementationSerializer::class) +enum class WalletImplementation(val wireValue: String) { + SmartWallet("smart-wallet"), + EOA("eoa"), + UNKNOWN_DEFAULT("UNKNOWN_DEFAULT"); + + companion object { + fun fromWireValue(value: String): WalletImplementation { + return values().find { it.wireValue == value } ?: UNKNOWN_DEFAULT + } + } +} + +object WalletImplementationSerializer : KSerializer { + override val descriptor: SerialDescriptor = + String.serializer().descriptor + + override fun serialize(encoder: Encoder, value: WalletImplementation) { + encoder.encodeSerializableValue( + String.serializer(), + value.wireValue, + ) + } + + override fun deserialize(decoder: Decoder): WalletImplementation { + return WalletImplementation.fromWireValue( + decoder.decodeSerializableValue( + String.serializer(), + ), + ) + } +} + + @Serializable(with = WalletStatusSerializer::class) enum class WalletStatus(val wireValue: String) { Active("active"), @@ -167,6 +236,108 @@ object KeyOriginSerializer : KSerializer { } +@Serializable(with = CiphersuiteSerializer::class) +enum class Ciphersuite(val wireValue: String) { + X25519_SHA256_AES_256_GCM("x25519-sha256-aes256gcm"), + X25519_SHA256_ChaCha20_Poly1305("x25519-sha256-chacha20poly1305"), + P256_SHA256_AES_256_GCM("p256-sha256-aes256gcm"), + P256_SHA256_ChaCha20_Poly1305("p256-sha256-chacha20poly1305"), + UNKNOWN_DEFAULT("UNKNOWN_DEFAULT"); + + companion object { + fun fromWireValue(value: String): Ciphersuite { + return values().find { it.wireValue == value } ?: UNKNOWN_DEFAULT + } + } +} + +object CiphersuiteSerializer : KSerializer { + override val descriptor: SerialDescriptor = + String.serializer().descriptor + + override fun serialize(encoder: Encoder, value: Ciphersuite) { + encoder.encodeSerializableValue( + String.serializer(), + value.wireValue, + ) + } + + override fun deserialize(decoder: Decoder): Ciphersuite { + return Ciphersuite.fromWireValue( + decoder.decodeSerializableValue( + String.serializer(), + ), + ) + } +} + + +@Serializable(with = TransportPurposeSerializer::class) +enum class TransportPurpose(val wireValue: String) { + WalletImport("wallet-import"), + UNKNOWN_DEFAULT("UNKNOWN_DEFAULT"); + + companion object { + fun fromWireValue(value: String): TransportPurpose { + return values().find { it.wireValue == value } ?: UNKNOWN_DEFAULT + } + } +} + +object TransportPurposeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + String.serializer().descriptor + + override fun serialize(encoder: Encoder, value: TransportPurpose) { + encoder.encodeSerializableValue( + String.serializer(), + value.wireValue, + ) + } + + override fun deserialize(decoder: Decoder): TransportPurpose { + return TransportPurpose.fromWireValue( + decoder.decodeSerializableValue( + String.serializer(), + ), + ) + } +} + + +@Serializable(with = KeyFormatSerializer::class) +enum class KeyFormat(val wireValue: String) { + PrivateKey("private-key"), + UNKNOWN_DEFAULT("UNKNOWN_DEFAULT"); + + companion object { + fun fromWireValue(value: String): KeyFormat { + return values().find { it.wireValue == value } ?: UNKNOWN_DEFAULT + } + } +} + +object KeyFormatSerializer : KSerializer { + override val descriptor: SerialDescriptor = + String.serializer().descriptor + + override fun serialize(encoder: Encoder, value: KeyFormat) { + encoder.encodeSerializableValue( + String.serializer(), + value.wireValue, + ) + } + + override fun deserialize(decoder: Decoder): KeyFormat { + return KeyFormat.fromWireValue( + decoder.decodeSerializableValue( + String.serializer(), + ), + ) + } +} + + @Serializable(with = IdentityTypeSerializer::class) enum class IdentityType(val wireValue: String) { Email("email"), @@ -309,6 +480,74 @@ object TransactionStatusSerializer : KSerializer { } +@Serializable(with = CredentialTypeSerializer::class) +enum class CredentialType(val wireValue: String) { + Direct("direct"), + Remote("remote"), + UNKNOWN_DEFAULT("UNKNOWN_DEFAULT"); + + companion object { + fun fromWireValue(value: String): CredentialType { + return values().find { it.wireValue == value } ?: UNKNOWN_DEFAULT + } + } +} + +object CredentialTypeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + String.serializer().descriptor + + override fun serialize(encoder: Encoder, value: CredentialType) { + encoder.encodeSerializableValue( + String.serializer(), + value.wireValue, + ) + } + + override fun deserialize(decoder: Decoder): CredentialType { + return CredentialType.fromWireValue( + decoder.decodeSerializableValue( + String.serializer(), + ), + ) + } +} + + +@Serializable(with = GrantKindSerializer::class) +enum class GrantKind(val wireValue: String) { + NativeTransfer("nativeTransfer"), + ERC20Transfer("erc20Transfer"), + UNKNOWN_DEFAULT("UNKNOWN_DEFAULT"); + + companion object { + fun fromWireValue(value: String): GrantKind { + return values().find { it.wireValue == value } ?: UNKNOWN_DEFAULT + } + } +} + +object GrantKindSerializer : KSerializer { + override val descriptor: SerialDescriptor = + String.serializer().descriptor + + override fun serialize(encoder: Encoder, value: GrantKind) { + encoder.encodeSerializableValue( + String.serializer(), + value.wireValue, + ) + } + + override fun deserialize(decoder: Decoder): GrantKind { + return GrantKind.fromWireValue( + decoder.decodeSerializableValue( + String.serializer(), + ), + ) + } +} + + @Serializable data class Identity( @@ -334,12 +573,32 @@ data class IdentityExtras( +@Serializable +data class HPKEPayload( + @SerialName("keyId") + val keyId: String, + @SerialName("suite") + val suite: Ciphersuite, + @SerialName("encapsulatedKey") + val encapsulatedKey: String, + @SerialName("ciphertext") + val ciphertext: String, +) + + + @Serializable data class Wallet( @SerialName("id") val id: String, @SerialName("type") - val type: WalletType, + val type: WalletType? = null, + @SerialName("networkFamily") + val networkFamily: NetworkFamily? = null, + @SerialName("implementation") + val implementation: WalletImplementation? = null, + @SerialName("keyOrigin") + val keyOrigin: KeyOrigin? = null, @SerialName("address") val address: String, @SerialName("reference") @@ -400,6 +659,8 @@ data class FeeOption( data class FeeOptionSelection( @SerialName("token") val token: String, + @SerialName("index") + val index: UInt? = null, ) @@ -414,6 +675,64 @@ data class Page( +@Serializable +data class CredentialMetadata( + @SerialName("appUrl") + val appUrl: String, + @SerialName("appName") + val appName: String, + @SerialName("appLogoUrl") + val appLogoUrl: String, + @SerialName("custom") + val custom: Map, +) + + + +@Serializable +data class NativeTransferGrant( + @SerialName("to") + val to: String, + @SerialName("limit") + val limit: String, +) + + + +@Serializable +data class ERC20TransferGrant( + @SerialName("token") + val token: String, + @SerialName("to") + val to: String? = null, + @SerialName("limit") + val limit: String, + @SerialName("cumulative") + val cumulative: Boolean? = null, +) + + + +@Serializable +data class Grant( + @SerialName("kind") + val kind: GrantKind, + @SerialName("nativeTransfer") + val nativeTransfer: NativeTransferGrant? = null, + @SerialName("erc20Transfer") + val erc20transfer: ERC20TransferGrant? = null, +) + + + +@Serializable +data class Grants( + @SerialName("entries") + val entries: List, +) + + + @Serializable data class CommitVerifierRequest( @SerialName("identityType") @@ -475,7 +794,11 @@ data class CompleteAuthResponse( @Serializable data class CreateWalletRequest( @SerialName("type") - val type: WalletType, + val type: WalletType? = null, + @SerialName("networkFamily") + val networkFamily: NetworkFamily? = null, + @SerialName("implementation") + val implementation: WalletImplementation? = null, @SerialName("reference") val reference: String? = null, ) @@ -490,6 +813,48 @@ data class CreateWalletResponse( +@Serializable +data class GetRecipientKeyRequest( + @SerialName("purpose") + val purpose: TransportPurpose, + @SerialName("suite") + val suite: Ciphersuite, +) + + + +@Serializable +data class GetRecipientKeyResponse( + @SerialName("keyId") + val keyId: String, + @SerialName("publicKey") + val publicKey: String, +) + + + +@Serializable +data class ImportWalletRequest( + @SerialName("networkFamily") + val networkFamily: NetworkFamily, + @SerialName("format") + val format: KeyFormat, + @SerialName("keyMaterial") + val keyMaterial: HPKEPayload, + @SerialName("reference") + val reference: String? = null, +) + + + +@Serializable +data class ImportWalletResponse( + @SerialName("wallet") + val wallet: Wallet, +) + + + @Serializable data class UseWalletRequest( @SerialName("walletId") @@ -562,6 +927,8 @@ data class ListAccessRequest( val walletId: String, @SerialName("page") val page: Page? = null, + @SerialName("type") + val type: CredentialType? = null, ) @@ -600,6 +967,8 @@ data class RevokeAccessRequest( val targetCredentialId: String, @SerialName("walletId") val walletId: String, + @SerialName("sessionId") + val sessionId: String? = null, ) @@ -613,28 +982,110 @@ data class RevokeAccessResponse( @Serializable -data class PrepareEthereumTransactionRequest( - @SerialName("network") - val network: String, - @SerialName("walletId") - val walletId: String, - @SerialName("to") - val to: String, - @SerialName("value") - val value: String, - @SerialName("data") - val `data`: String? = null, - @SerialName("mode") - val mode: TransactionMode, +data class RegisterCredentialRequest( + @SerialName("lifetime") + val lifetime: UInt, + @SerialName("metadata") + val metadata: CredentialMetadata, ) @Serializable -data class PrepareEthereumContractCallRequest( - @SerialName("network") - val network: String, - @SerialName("walletId") +data class RegisterCredentialResponse( + @SerialName("credentialId") + val credentialId: String, +) + + + +@Serializable +data class InspectCredentialRequest( + @SerialName("scope") + val scope: String, + @SerialName("credentialId") + val credentialId: String, +) + + + +@Serializable +data class InspectCredentialResponse( + @SerialName("metadata") + val metadata: CredentialMetadata, +) + + + +@Serializable +data class AuthorizeRemoteAccessRequest( + @SerialName("credentialId") + val credentialId: String, + @SerialName("walletId") + val walletId: String, + @SerialName("grants") + val grants: Grants, + @SerialName("expiry") + val expiry: String, + @SerialName("chainId") + val chainId: String, + @SerialName("sessionId") + val sessionId: String? = null, +) + + + +@Serializable +data class AuthorizeRemoteAccessResponse( + @SerialName("sessionId") + val sessionId: String, + @SerialName("expiry") + val expiry: String, +) + + + +@Serializable +data class RevokeCredentialRequest( + @SerialName("credentialId") + val credentialId: String, +) + + + +@Serializable +data class RevokeCredentialResponse( + @SerialName("ok") + val ok: Boolean, +) + + + +@Serializable +data class PrepareEthereumTransactionRequest( + @SerialName("network") + val network: String, + @SerialName("walletId") + val walletId: String, + @SerialName("to") + val to: String, + @SerialName("value") + val value: String, + @SerialName("data") + val `data`: String? = null, + @SerialName("mode") + val mode: TransactionMode, + @SerialName("sessionId") + val sessionId: String? = null, +) + + + +@Serializable +data class PrepareEthereumContractCallRequest( + @SerialName("network") + val network: String, + @SerialName("walletId") val walletId: String, @SerialName("contract") val contract: String, @@ -644,6 +1095,34 @@ data class PrepareEthereumContractCallRequest( val args: List? = null, @SerialName("mode") val mode: TransactionMode, + @SerialName("sessionId") + val sessionId: String? = null, +) + + + +@Serializable +data class SolanaRecipient( + @SerialName("address") + val address: String, +) + + + +@Serializable +data class PrepareSolanaTransferRequest( + @SerialName("network") + val network: String, + @SerialName("walletId") + val walletId: String, + @SerialName("asset") + val asset: String, + @SerialName("recipient") + val recipient: SolanaRecipient, + @SerialName("amount") + val amount: String, + @SerialName("mode") + val mode: TransactionMode, ) @@ -704,6 +1183,14 @@ data class TransactionStatusResponse( data class CredentialInfo( @SerialName("credentialId") val credentialId: String, + @SerialName("sessionId") + val sessionId: String? = null, + @SerialName("type") + val type: CredentialType, + @SerialName("metadata") + val metadata: CredentialMetadata? = null, + @SerialName("grants") + val grants: Grants? = null, @SerialName("expiresAt") val expiresAt: String, @SerialName("isCaller") @@ -712,10 +1199,92 @@ data class CredentialInfo( +@Serializable +data class SessionInfo( + @SerialName("sessionId") + val sessionId: String, + @SerialName("walletId") + val walletId: String, + @SerialName("signerAddress") + val signerAddress: String, + @SerialName("grants") + val grants: Grants, + @SerialName("chainId") + val chainId: String, + @SerialName("expiresAt") + val expiresAt: String, +) + + + +@Serializable +data class ListSessionsRequest( + @SerialName("page") + val page: Page? = null, +) + + + +@Serializable +data class ListSessionsResponse( + @SerialName("sessions") + val sessions: List, + @SerialName("page") + val page: Page? = null, +) + + + +@Serializable +data class GetSessionRequest( + @SerialName("sessionId") + val sessionId: String, +) + + + +@Serializable +data class GetSessionResponse( + @SerialName("session") + val session: SessionInfo, +) + + + +@Serializable +data class GetSessionUsageRequest( + @SerialName("sessionId") + val sessionId: String, + @SerialName("network") + val network: String, +) + + + +@Serializable +data class GrantUsage( + @SerialName("grant") + val grant: Grant, + @SerialName("used") + val used: String? = null, +) + + + +@Serializable +data class GetSessionUsageResponse( + @SerialName("entries") + val entries: List, +) + + + @Serializable data class IsValidMessageSignatureRequest( @SerialName("network") val network: String? = null, + @SerialName("networkFamily") + val networkFamily: NetworkFamily? = null, @SerialName("walletAddress") val walletAddress: String? = null, @SerialName("walletId") @@ -910,6 +1479,8 @@ enum class ErrorKind(val code: Int) { TRANSACTION_EXPIRED(7309), INVALID_FEE_OPTION(7310), TRANSACTION_BROADCAST_FAILED(7311), + UNSUPPORTED_ASSET(7312), + ADDRESS_ALREADY_IMPORTED(7313), UNKNOWN(-999); companion object { @@ -996,6 +1567,114 @@ object WaasApi { } } + object RevokeCredential { + const val path: String = "/RevokeCredential" + const val urlPath: String = "/v1/Waas/RevokeCredential" + fun encodeRequest(request: RevokeCredentialRequest, json: Json = WebRpcJson): String { + return json.encodeToString(request) + } + + fun decodeResponse(body: String, json: Json = WebRpcJson): RevokeCredentialResponse { + return json.decodeFromString(body) + } + } + + object RegisterCredential { + const val path: String = "/RegisterCredential" + const val urlPath: String = "/v1/Waas/RegisterCredential" + fun encodeRequest(request: RegisterCredentialRequest, json: Json = WebRpcJson): String { + return json.encodeToString(request) + } + + fun decodeResponse(body: String, json: Json = WebRpcJson): RegisterCredentialResponse { + return json.decodeFromString(body) + } + } + + object AuthorizeRemoteAccess { + const val path: String = "/AuthorizeRemoteAccess" + const val urlPath: String = "/v1/Waas/AuthorizeRemoteAccess" + fun encodeRequest(request: AuthorizeRemoteAccessRequest, json: Json = WebRpcJson): String { + return json.encodeToString(request) + } + + fun decodeResponse(body: String, json: Json = WebRpcJson): AuthorizeRemoteAccessResponse { + return json.decodeFromString(body) + } + } + + object ListAccess { + const val path: String = "/ListAccess" + const val urlPath: String = "/v1/Waas/ListAccess" + fun encodeRequest(request: ListAccessRequest, json: Json = WebRpcJson): String { + return json.encodeToString(request) + } + + fun decodeResponse(body: String, json: Json = WebRpcJson): ListAccessResponse { + return json.decodeFromString(body) + } + } + + object RevokeAccess { + const val path: String = "/RevokeAccess" + const val urlPath: String = "/v1/Waas/RevokeAccess" + fun encodeRequest(request: RevokeAccessRequest, json: Json = WebRpcJson): String { + return json.encodeToString(request) + } + + fun decodeResponse(body: String, json: Json = WebRpcJson): RevokeAccessResponse { + return json.decodeFromString(body) + } + } + + object ListWallets { + const val path: String = "/ListWallets" + const val urlPath: String = "/v1/Waas/ListWallets" + fun encodeRequest(request: ListWalletsRequest, json: Json = WebRpcJson): String { + return json.encodeToString(request) + } + + fun decodeResponse(body: String, json: Json = WebRpcJson): ListWalletsResponse { + return json.decodeFromString(body) + } + } + + object ListSessions { + const val path: String = "/ListSessions" + const val urlPath: String = "/v1/Waas/ListSessions" + fun encodeRequest(request: ListSessionsRequest, json: Json = WebRpcJson): String { + return json.encodeToString(request) + } + + fun decodeResponse(body: String, json: Json = WebRpcJson): ListSessionsResponse { + return json.decodeFromString(body) + } + } + + object GetSession { + const val path: String = "/GetSession" + const val urlPath: String = "/v1/Waas/GetSession" + fun encodeRequest(request: GetSessionRequest, json: Json = WebRpcJson): String { + return json.encodeToString(request) + } + + fun decodeResponse(body: String, json: Json = WebRpcJson): GetSessionResponse { + return json.decodeFromString(body) + } + } + + object GetSessionUsage { + const val path: String = "/GetSessionUsage" + const val urlPath: String = "/v1/Waas/GetSessionUsage" + fun encodeRequest(request: GetSessionUsageRequest, json: Json = WebRpcJson): String { + return json.encodeToString(request) + } + + fun decodeResponse(body: String, json: Json = WebRpcJson): GetSessionUsageResponse { + return json.decodeFromString(body) + } + } + object CreateWallet { const val path: String = "/CreateWallet" const val urlPath: String = "/v1/Waas/CreateWallet" @@ -1020,6 +1699,30 @@ object WaasApi { } } + object GetRecipientKey { + const val path: String = "/GetRecipientKey" + const val urlPath: String = "/v1/Waas/GetRecipientKey" + fun encodeRequest(request: GetRecipientKeyRequest, json: Json = WebRpcJson): String { + return json.encodeToString(request) + } + + fun decodeResponse(body: String, json: Json = WebRpcJson): GetRecipientKeyResponse { + return json.decodeFromString(body) + } + } + + object ImportWallet { + const val path: String = "/ImportWallet" + const val urlPath: String = "/v1/Waas/ImportWallet" + fun encodeRequest(request: ImportWalletRequest, json: Json = WebRpcJson): String { + return json.encodeToString(request) + } + + fun decodeResponse(body: String, json: Json = WebRpcJson): ImportWalletResponse { + return json.decodeFromString(body) + } + } + object SignMessage { const val path: String = "/SignMessage" const val urlPath: String = "/v1/Waas/SignMessage" @@ -1068,6 +1771,18 @@ object WaasApi { } } + object PrepareSolanaTransfer { + const val path: String = "/PrepareSolanaTransfer" + const val urlPath: String = "/v1/Waas/PrepareSolanaTransfer" + fun encodeRequest(request: PrepareSolanaTransferRequest, json: Json = WebRpcJson): String { + return json.encodeToString(request) + } + + fun decodeResponse(body: String, json: Json = WebRpcJson): PrepareResponse { + return json.decodeFromString(body) + } + } + object Execute { const val path: String = "/Execute" const val urlPath: String = "/v1/Waas/Execute" @@ -1092,42 +1807,6 @@ object WaasApi { } } - object ListAccess { - const val path: String = "/ListAccess" - const val urlPath: String = "/v1/Waas/ListAccess" - fun encodeRequest(request: ListAccessRequest, json: Json = WebRpcJson): String { - return json.encodeToString(request) - } - - fun decodeResponse(body: String, json: Json = WebRpcJson): ListAccessResponse { - return json.decodeFromString(body) - } - } - - object RevokeAccess { - const val path: String = "/RevokeAccess" - const val urlPath: String = "/v1/Waas/RevokeAccess" - fun encodeRequest(request: RevokeAccessRequest, json: Json = WebRpcJson): String { - return json.encodeToString(request) - } - - fun decodeResponse(body: String, json: Json = WebRpcJson): RevokeAccessResponse { - return json.decodeFromString(body) - } - } - - object ListWallets { - const val path: String = "/ListWallets" - const val urlPath: String = "/v1/Waas/ListWallets" - fun encodeRequest(request: ListWalletsRequest, json: Json = WebRpcJson): String { - return json.encodeToString(request) - } - - fun decodeResponse(body: String, json: Json = WebRpcJson): ListWalletsResponse { - return json.decodeFromString(body) - } - } - object GetIDToken { const val path: String = "/GetIDToken" const val urlPath: String = "/v1/Waas/GetIDToken" @@ -1178,6 +1857,141 @@ class WaasClient( ) } + @Throws(WebRpcError::class, WebRpcTransportException::class) + suspend fun revokeCredential(request:RevokeCredentialRequest): RevokeCredentialResponse { + return executeWebRpc( + baseUrl = baseUrl, + urlPath = WaasApi.RevokeCredential.urlPath, + body = WaasApi.RevokeCredential.encodeRequest(request, json), + transport = transport, + headers = headers(), + json = json, + decodeSuccess = { body, decodeJson -> + WaasApi.RevokeCredential.decodeResponse(body, decodeJson) + }, + ) + } + + @Throws(WebRpcError::class, WebRpcTransportException::class) + suspend fun registerCredential(request:RegisterCredentialRequest): RegisterCredentialResponse { + return executeWebRpc( + baseUrl = baseUrl, + urlPath = WaasApi.RegisterCredential.urlPath, + body = WaasApi.RegisterCredential.encodeRequest(request, json), + transport = transport, + headers = headers(), + json = json, + decodeSuccess = { body, decodeJson -> + WaasApi.RegisterCredential.decodeResponse(body, decodeJson) + }, + ) + } + + @Throws(WebRpcError::class, WebRpcTransportException::class) + suspend fun authorizeRemoteAccess(request:AuthorizeRemoteAccessRequest): AuthorizeRemoteAccessResponse { + return executeWebRpc( + baseUrl = baseUrl, + urlPath = WaasApi.AuthorizeRemoteAccess.urlPath, + body = WaasApi.AuthorizeRemoteAccess.encodeRequest(request, json), + transport = transport, + headers = headers(), + json = json, + decodeSuccess = { body, decodeJson -> + WaasApi.AuthorizeRemoteAccess.decodeResponse(body, decodeJson) + }, + ) + } + + @Throws(WebRpcError::class, WebRpcTransportException::class) + suspend fun listAccess(request:ListAccessRequest): ListAccessResponse { + return executeWebRpc( + baseUrl = baseUrl, + urlPath = WaasApi.ListAccess.urlPath, + body = WaasApi.ListAccess.encodeRequest(request, json), + transport = transport, + headers = headers(), + json = json, + decodeSuccess = { body, decodeJson -> + WaasApi.ListAccess.decodeResponse(body, decodeJson) + }, + ) + } + + @Throws(WebRpcError::class, WebRpcTransportException::class) + suspend fun revokeAccess(request:RevokeAccessRequest): RevokeAccessResponse { + return executeWebRpc( + baseUrl = baseUrl, + urlPath = WaasApi.RevokeAccess.urlPath, + body = WaasApi.RevokeAccess.encodeRequest(request, json), + transport = transport, + headers = headers(), + json = json, + decodeSuccess = { body, decodeJson -> + WaasApi.RevokeAccess.decodeResponse(body, decodeJson) + }, + ) + } + + @Throws(WebRpcError::class, WebRpcTransportException::class) + suspend fun listWallets(request:ListWalletsRequest): ListWalletsResponse { + return executeWebRpc( + baseUrl = baseUrl, + urlPath = WaasApi.ListWallets.urlPath, + body = WaasApi.ListWallets.encodeRequest(request, json), + transport = transport, + headers = headers(), + json = json, + decodeSuccess = { body, decodeJson -> + WaasApi.ListWallets.decodeResponse(body, decodeJson) + }, + ) + } + + @Throws(WebRpcError::class, WebRpcTransportException::class) + suspend fun listSessions(request:ListSessionsRequest): ListSessionsResponse { + return executeWebRpc( + baseUrl = baseUrl, + urlPath = WaasApi.ListSessions.urlPath, + body = WaasApi.ListSessions.encodeRequest(request, json), + transport = transport, + headers = headers(), + json = json, + decodeSuccess = { body, decodeJson -> + WaasApi.ListSessions.decodeResponse(body, decodeJson) + }, + ) + } + + @Throws(WebRpcError::class, WebRpcTransportException::class) + suspend fun getSession(request:GetSessionRequest): GetSessionResponse { + return executeWebRpc( + baseUrl = baseUrl, + urlPath = WaasApi.GetSession.urlPath, + body = WaasApi.GetSession.encodeRequest(request, json), + transport = transport, + headers = headers(), + json = json, + decodeSuccess = { body, decodeJson -> + WaasApi.GetSession.decodeResponse(body, decodeJson) + }, + ) + } + + @Throws(WebRpcError::class, WebRpcTransportException::class) + suspend fun getSessionUsage(request:GetSessionUsageRequest): GetSessionUsageResponse { + return executeWebRpc( + baseUrl = baseUrl, + urlPath = WaasApi.GetSessionUsage.urlPath, + body = WaasApi.GetSessionUsage.encodeRequest(request, json), + transport = transport, + headers = headers(), + json = json, + decodeSuccess = { body, decodeJson -> + WaasApi.GetSessionUsage.decodeResponse(body, decodeJson) + }, + ) + } + @Throws(WebRpcError::class, WebRpcTransportException::class) suspend fun createWallet(request:CreateWalletRequest): CreateWalletResponse { return executeWebRpc( @@ -1209,136 +2023,136 @@ class WaasClient( } @Throws(WebRpcError::class, WebRpcTransportException::class) - suspend fun signMessage(request:SignMessageRequest): SignMessageResponse { + suspend fun getRecipientKey(request:GetRecipientKeyRequest): GetRecipientKeyResponse { return executeWebRpc( baseUrl = baseUrl, - urlPath = WaasApi.SignMessage.urlPath, - body = WaasApi.SignMessage.encodeRequest(request, json), + urlPath = WaasApi.GetRecipientKey.urlPath, + body = WaasApi.GetRecipientKey.encodeRequest(request, json), transport = transport, headers = headers(), json = json, decodeSuccess = { body, decodeJson -> - WaasApi.SignMessage.decodeResponse(body, decodeJson) + WaasApi.GetRecipientKey.decodeResponse(body, decodeJson) }, ) } @Throws(WebRpcError::class, WebRpcTransportException::class) - suspend fun signTypedData(request:SignTypedDataRequest): SignTypedDataResponse { + suspend fun importWallet(request:ImportWalletRequest): ImportWalletResponse { return executeWebRpc( baseUrl = baseUrl, - urlPath = WaasApi.SignTypedData.urlPath, - body = WaasApi.SignTypedData.encodeRequest(request, json), + urlPath = WaasApi.ImportWallet.urlPath, + body = WaasApi.ImportWallet.encodeRequest(request, json), transport = transport, headers = headers(), json = json, decodeSuccess = { body, decodeJson -> - WaasApi.SignTypedData.decodeResponse(body, decodeJson) + WaasApi.ImportWallet.decodeResponse(body, decodeJson) }, ) } @Throws(WebRpcError::class, WebRpcTransportException::class) - suspend fun prepareEthereumTransaction(request:PrepareEthereumTransactionRequest): PrepareResponse { + suspend fun signMessage(request:SignMessageRequest): SignMessageResponse { return executeWebRpc( baseUrl = baseUrl, - urlPath = WaasApi.PrepareEthereumTransaction.urlPath, - body = WaasApi.PrepareEthereumTransaction.encodeRequest(request, json), + urlPath = WaasApi.SignMessage.urlPath, + body = WaasApi.SignMessage.encodeRequest(request, json), transport = transport, headers = headers(), json = json, decodeSuccess = { body, decodeJson -> - WaasApi.PrepareEthereumTransaction.decodeResponse(body, decodeJson) + WaasApi.SignMessage.decodeResponse(body, decodeJson) }, ) } @Throws(WebRpcError::class, WebRpcTransportException::class) - suspend fun prepareEthereumContractCall(request:PrepareEthereumContractCallRequest): PrepareResponse { + suspend fun signTypedData(request:SignTypedDataRequest): SignTypedDataResponse { return executeWebRpc( baseUrl = baseUrl, - urlPath = WaasApi.PrepareEthereumContractCall.urlPath, - body = WaasApi.PrepareEthereumContractCall.encodeRequest(request, json), + urlPath = WaasApi.SignTypedData.urlPath, + body = WaasApi.SignTypedData.encodeRequest(request, json), transport = transport, headers = headers(), json = json, decodeSuccess = { body, decodeJson -> - WaasApi.PrepareEthereumContractCall.decodeResponse(body, decodeJson) + WaasApi.SignTypedData.decodeResponse(body, decodeJson) }, ) } @Throws(WebRpcError::class, WebRpcTransportException::class) - suspend fun execute(request:ExecuteRequest): ExecuteResponse { + suspend fun prepareEthereumTransaction(request:PrepareEthereumTransactionRequest): PrepareResponse { return executeWebRpc( baseUrl = baseUrl, - urlPath = WaasApi.Execute.urlPath, - body = WaasApi.Execute.encodeRequest(request, json), + urlPath = WaasApi.PrepareEthereumTransaction.urlPath, + body = WaasApi.PrepareEthereumTransaction.encodeRequest(request, json), transport = transport, headers = headers(), json = json, decodeSuccess = { body, decodeJson -> - WaasApi.Execute.decodeResponse(body, decodeJson) + WaasApi.PrepareEthereumTransaction.decodeResponse(body, decodeJson) }, ) } @Throws(WebRpcError::class, WebRpcTransportException::class) - suspend fun transactionStatus(request:TransactionStatusRequest): TransactionStatusResponse { + suspend fun prepareEthereumContractCall(request:PrepareEthereumContractCallRequest): PrepareResponse { return executeWebRpc( baseUrl = baseUrl, - urlPath = WaasApi.TransactionStatusMethod.urlPath, - body = WaasApi.TransactionStatusMethod.encodeRequest(request, json), + urlPath = WaasApi.PrepareEthereumContractCall.urlPath, + body = WaasApi.PrepareEthereumContractCall.encodeRequest(request, json), transport = transport, headers = headers(), json = json, decodeSuccess = { body, decodeJson -> - WaasApi.TransactionStatusMethod.decodeResponse(body, decodeJson) + WaasApi.PrepareEthereumContractCall.decodeResponse(body, decodeJson) }, ) } @Throws(WebRpcError::class, WebRpcTransportException::class) - suspend fun listAccess(request:ListAccessRequest): ListAccessResponse { + suspend fun prepareSolanaTransfer(request:PrepareSolanaTransferRequest): PrepareResponse { return executeWebRpc( baseUrl = baseUrl, - urlPath = WaasApi.ListAccess.urlPath, - body = WaasApi.ListAccess.encodeRequest(request, json), + urlPath = WaasApi.PrepareSolanaTransfer.urlPath, + body = WaasApi.PrepareSolanaTransfer.encodeRequest(request, json), transport = transport, headers = headers(), json = json, decodeSuccess = { body, decodeJson -> - WaasApi.ListAccess.decodeResponse(body, decodeJson) + WaasApi.PrepareSolanaTransfer.decodeResponse(body, decodeJson) }, ) } @Throws(WebRpcError::class, WebRpcTransportException::class) - suspend fun revokeAccess(request:RevokeAccessRequest): RevokeAccessResponse { + suspend fun execute(request:ExecuteRequest): ExecuteResponse { return executeWebRpc( baseUrl = baseUrl, - urlPath = WaasApi.RevokeAccess.urlPath, - body = WaasApi.RevokeAccess.encodeRequest(request, json), + urlPath = WaasApi.Execute.urlPath, + body = WaasApi.Execute.encodeRequest(request, json), transport = transport, headers = headers(), json = json, decodeSuccess = { body, decodeJson -> - WaasApi.RevokeAccess.decodeResponse(body, decodeJson) + WaasApi.Execute.decodeResponse(body, decodeJson) }, ) } @Throws(WebRpcError::class, WebRpcTransportException::class) - suspend fun listWallets(request:ListWalletsRequest): ListWalletsResponse { + suspend fun transactionStatus(request:TransactionStatusRequest): TransactionStatusResponse { return executeWebRpc( baseUrl = baseUrl, - urlPath = WaasApi.ListWallets.urlPath, - body = WaasApi.ListWallets.encodeRequest(request, json), + urlPath = WaasApi.TransactionStatusMethod.urlPath, + body = WaasApi.TransactionStatusMethod.encodeRequest(request, json), transport = transport, headers = headers(), json = json, decodeSuccess = { body, decodeJson -> - WaasApi.ListWallets.decodeResponse(body, decodeJson) + WaasApi.TransactionStatusMethod.decodeResponse(body, decodeJson) }, ) } @@ -1432,6 +2246,18 @@ object WaasPublicApi { return json.decodeFromString(body) } } + + object InspectCredential { + const val path: String = "/InspectCredential" + const val urlPath: String = "/v1/WaasPublic/InspectCredential" + fun encodeRequest(request: InspectCredentialRequest, json: Json = WebRpcJson): String { + return json.encodeToString(request) + } + + fun decodeResponse(body: String, json: Json = WebRpcJson): InspectCredentialResponse { + return json.decodeFromString(body) + } + } } class WaasPublicClient( @@ -1500,7 +2326,21 @@ class WaasPublicClient( }, ) } + + @Throws(WebRpcError::class, WebRpcTransportException::class) + suspend fun inspectCredential(request:InspectCredentialRequest): InspectCredentialResponse { + return executeWebRpc( + baseUrl = baseUrl, + urlPath = WaasPublicApi.InspectCredential.urlPath, + body = WaasPublicApi.InspectCredential.encodeRequest(request, json), + transport = transport, + headers = headers(), + json = json, + decodeSuccess = { body, decodeJson -> + WaasPublicApi.InspectCredential.decodeResponse(body, decodeJson) + }, + ) + } } // endregion - diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/OMSWalletModels.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/OMSWalletModels.kt index 1817969..1c724b8 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/OMSWalletModels.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/OMSWalletModels.kt @@ -7,6 +7,16 @@ enum class WalletType( val wireValue: String, ) { Ethereum("ethereum"), + Solana("solana"), + UNKNOWN_DEFAULT("UNKNOWN_DEFAULT"), +} + +/** Whether a wallet key was created in WaaS custody or imported by its owner. */ +enum class WalletKeyOrigin( + val wireValue: String, +) { + Enclave("enclave"), + Imported("imported"), UNKNOWN_DEFAULT("UNKNOWN_DEFAULT"), } @@ -33,6 +43,7 @@ data class Wallet( val type: WalletType, val address: String, val reference: String? = null, + val keyOrigin: WalletKeyOrigin, ) data class FeeToken( @@ -54,8 +65,12 @@ data class FeeOption( data class FeeOptionSelection( val token: String, + val index: UInt? = null, ) { - constructor(feeOption: FeeOption) : this(token = feeOption.selectionToken()) + constructor(feeOption: FeeOption, index: UInt? = null) : this( + token = feeOption.selectionToken(), + index = index, + ) } data class Page( @@ -68,17 +83,87 @@ data class AbiArg( val value: JsonElement, ) -data class CredentialInfo( +/** A credential currently authorized to use the selected wallet. */ +data class WalletCredential( val credentialId: String, val expiresAt: String, val isCaller: Boolean, ) -data class ListAccessResponse( - val credentials: List, +/** Display metadata supplied by a remote application credential. */ +data class RemoteCredentialMetadata( + val appUrl: String, + val appName: String, + val appLogoUrl: String, + val custom: Map, +) + +/** Owner-approved EVM operation allowed during a bounded smart session. */ +sealed interface SmartSessionGrant { + data class NativeTransfer( + val to: String, + val limit: BigInteger, + ) : SmartSessionGrant + + data class Erc20Transfer( + val token: String, + val to: String? = null, + val limit: BigInteger, + val cumulative: Boolean? = null, + ) : SmartSessionGrant +} + +/** Filter for direct or remotely authorized wallet access. */ +enum class AccessGrantType { + Direct, + Remote, +} + +/** Direct or remote credential access associated with a wallet. */ +sealed interface AccessGrant { + val credential: WalletCredential + + data class Direct( + override val credential: WalletCredential, + ) : AccessGrant + + data class Remote( + override val credential: WalletCredential, + val sessionId: String, + val metadata: RemoteCredentialMetadata, + val grants: List, + ) : AccessGrant +} + +/** One page of wallet access grants and its continuation cursor. */ +data class AccessGrantPage( + val grants: List, val page: Page? = null, ) +/** Identifiers returned after an owner authorizes a remote smart session. */ +data class AuthorizedRemoteAccess( + val walletId: String, + val sessionId: String, + val expiresAt: String, +) + +/** Owner-visible details for one authorized smart session. */ +data class RemoteAccessSession( + val sessionId: String, + val walletId: String, + val signerAddress: String, + val grants: List, + val chainId: Int, + val expiresAt: String, +) + +/** Current usage for one bounded smart-session grant. */ +data class SmartSessionGrantUsage( + val grant: SmartSessionGrant, + val used: BigInteger? = null, +) + data class TransactionStatusResponse( val status: TransactionStatus, val txnHash: String? = null, @@ -101,14 +186,12 @@ fun interface FeeOptionSelector { data class FeeOptionWithBalance( val feeOption: FeeOption, - val balance: TokenBalance?, - val available: String?, - val availableRaw: String?, - val decimals: Int?, -) { - val selection: FeeOptionSelection - get() = FeeOptionSelection(feeOption) -} + val selection: FeeOptionSelection = FeeOptionSelection(feeOption), + val balance: TokenBalance? = null, + val available: String? = null, + val availableRaw: String? = null, + val decimals: Int? = null, +) private fun FeeOptionWithBalance.hasEnoughBalance(): Boolean { val balance = availableRaw?.toBigIntegerOrNull() ?: return false diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/SolanaIndexerModels.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/SolanaIndexerModels.kt new file mode 100644 index 0000000..8d55174 --- /dev/null +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/SolanaIndexerModels.kt @@ -0,0 +1,89 @@ +package technology.polygon.omswallet.models + +import technology.polygon.omswallet.SolanaNetwork + +/** Verification state assigned to Solana asset metadata. */ +enum class SolanaVerificationStatus { + Verified, + Unverified, + Unknown, +} + +/** Source used to verify Solana asset metadata. */ +enum class SolanaVerificationSource { + Jupiter, + SolflareUtl, + None, +} + +/** Token program owning a Solana mint. */ +enum class SolanaTokenProgram { + SplToken, + Token2022, +} + +/** Common public fields returned for a Solana balance. */ +sealed interface SolanaBalance { + val network: SolanaNetwork + val accountAddress: String + val name: String + val symbol: String + val decimals: Int + val balance: String + val formattedBalance: String + val imageUrl: String? + val metadataUri: String? + val verificationStatus: SolanaVerificationStatus + val verificationSource: SolanaVerificationSource + val priceUSD: String? + val balanceUSD: String? + + /** Native SOL balance. */ + data class Native( + override val network: SolanaNetwork, + override val accountAddress: String, + override val name: String, + override val symbol: String, + override val decimals: Int, + override val balance: String, + override val formattedBalance: String, + override val imageUrl: String?, + override val metadataUri: String?, + override val verificationStatus: SolanaVerificationStatus, + override val verificationSource: SolanaVerificationSource, + override val priceUSD: String?, + override val balanceUSD: String?, + ) : SolanaBalance + + /** SPL Token or Token-2022 balance. */ + data class FungibleToken( + override val network: SolanaNetwork, + override val accountAddress: String, + val tokenProgram: SolanaTokenProgram, + val mintAddress: String, + override val name: String, + override val symbol: String, + override val decimals: Int, + override val balance: String, + override val formattedBalance: String, + override val imageUrl: String?, + override val metadataUri: String?, + override val verificationStatus: SolanaVerificationStatus, + override val verificationSource: SolanaVerificationSource, + override val priceUSD: String?, + override val balanceUSD: String?, + ) : SolanaBalance +} + +/** Per-network failure returned alongside partial Solana balance results. */ +data class SolanaNetworkError( + val network: SolanaNetwork, + val reason: String, +) + +/** Solana balances and partial network errors returned by the gateway. */ +data class SolanaBalancesResult( + val status: Int, + val balances: List, + val errors: List, +) diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/WalletImportModels.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/WalletImportModels.kt new file mode 100644 index 0000000..02c0794 --- /dev/null +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/WalletImportModels.kt @@ -0,0 +1,59 @@ +package technology.polygon.omswallet.models + +/** HPKE cipher suites accepted by the wallet-import transport. */ +enum class WalletImportCipherSuite( + val wireValue: String, +) { + X25519Sha256Aes256Gcm("x25519-sha256-aes256gcm"), + X25519Sha256ChaCha20Poly1305("x25519-sha256-chacha20poly1305"), + P256Sha256Aes256Gcm("p256-sha256-aes256gcm"), + P256Sha256ChaCha20Poly1305("p256-sha256-chacha20poly1305"), +} + +/** Plaintext private-key input for high-level wallet import. */ +sealed interface WalletImportPrivateKey { + val walletType: WalletType + + /** Ethereum private key supplied as hexadecimal text. */ + data class Ethereum( + val value: String, + ) : WalletImportPrivateKey { + override val walletType: WalletType = WalletType.Ethereum + } + + /** Ethereum private key supplied as 32 raw bytes. */ + class EthereumBytes( + val value: ByteArray, + ) : WalletImportPrivateKey { + override val walletType: WalletType = WalletType.Ethereum + } + + /** Solana seed or keypair supplied as base58 text. */ + data class Solana( + val value: String, + ) : WalletImportPrivateKey { + override val walletType: WalletType = WalletType.Solana + } + + /** Solana seed or keypair supplied as 32 or 64 raw bytes. */ + class SolanaBytes( + val value: ByteArray, + ) : WalletImportPrivateKey { + override val walletType: WalletType = WalletType.Solana + } +} + +/** Attested public key returned for an advanced wallet-import encryption flow. */ +data class WalletImportRecipientKey( + val keyId: String, + val cipherSuite: WalletImportCipherSuite, + val publicKey: String, +) + +/** Caller-encrypted private-key material accepted by advanced wallet import. */ +data class EncryptedWalletImportKeyMaterial( + val keyId: String, + val cipherSuite: WalletImportCipherSuite, + val encapsulatedKey: String, + val ciphertext: String, +) diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/network/OMSWalletEnvironment.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/network/OMSWalletEnvironment.kt index 26f4082..a5f8356 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/network/OMSWalletEnvironment.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/network/OMSWalletEnvironment.kt @@ -6,6 +6,7 @@ import java.net.URI internal class OMSWalletEnvironment( val walletApiUrl: String, val indexerGatewayUrl: String, + val solanaIndexerGatewayUrl: String = "${walletApiUrl.trimEnd('/')}/v1/SolanaIndexerGateway/", ) { internal fun walletApiBaseUrl(): String { val uri = URI(walletApiUrl) @@ -17,16 +18,20 @@ internal class OMSWalletEnvironment( if (other !is OMSWalletEnvironment) return false return walletApiBaseUrl() == other.walletApiBaseUrl() && - indexerGatewayUrl == other.indexerGatewayUrl + indexerGatewayUrl == other.indexerGatewayUrl && + solanaIndexerGatewayUrl == other.solanaIndexerGatewayUrl } override fun hashCode(): Int { var result = walletApiBaseUrl().hashCode() result = 31 * result + indexerGatewayUrl.hashCode() + result = 31 * result + solanaIndexerGatewayUrl.hashCode() return result } - override fun toString(): String = "OMSWalletEnvironment(walletApiUrl=$walletApiUrl, indexerGatewayUrl=$indexerGatewayUrl)" + override fun toString(): String = + "OMSWalletEnvironment(walletApiUrl=$walletApiUrl, indexerGatewayUrl=$indexerGatewayUrl, " + + "solanaIndexerGatewayUrl=$solanaIndexerGatewayUrl)" companion object { internal const val accessKeyHeaderName: String = "Api-Key" @@ -38,6 +43,7 @@ internal class OMSWalletEnvironment( return OMSWalletEnvironment( walletApiUrl = parsed.walletApiUrl, indexerGatewayUrl = parsed.indexerGatewayUrl, + solanaIndexerGatewayUrl = parsed.solanaIndexerGatewayUrl, ) } } diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/network/OMSWalletHttpClient.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/network/OMSWalletHttpClient.kt index 805b4b7..24055c3 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/network/OMSWalletHttpClient.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/network/OMSWalletHttpClient.kt @@ -10,6 +10,7 @@ import okhttp3.RequestBody.Companion.toRequestBody internal data class OMSWalletHttpResponse( val statusCode: Int, val body: String, + val headers: Map, ) internal class OMSWalletHttpClient( @@ -32,10 +33,11 @@ internal class OMSWalletHttpClient( }.build() okHttpClient.newCall(request).execute().use { response -> - val responseBody = response.body?.string().orEmpty() + val responseBody = response.body.string() OMSWalletHttpResponse( statusCode = response.code, body = responseBody, + headers = response.headers.associate { it.first.lowercase() to it.second }, ) } } diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/session/OMSWalletSession.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/session/OMSWalletSession.kt index 7f75900..3a0eaa1 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/session/OMSWalletSession.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/session/OMSWalletSession.kt @@ -12,6 +12,8 @@ internal data class OMSWalletSessionSnapshot( val signerKeyType: WalletSigningAlgorithm? = null, val expiresAt: String? = null, val auth: OMSWalletSessionAuth? = null, + val pendingWalletSelectionId: Long? = null, + val pendingWalletType: technology.polygon.omswallet.models.WalletType? = null, ) internal data class OMSWalletPendingAuthSnapshot( @@ -50,6 +52,7 @@ internal class OMSWalletSession( val expiresAt: String, val auth: OMSWalletSessionAuth, val pendingWalletSelectionId: Long?, + val walletType: technology.polygon.omswallet.models.WalletType?, ) : SessionState { override fun snapshot(): OMSWalletSessionSnapshot = OMSWalletSessionSnapshot( @@ -57,6 +60,8 @@ internal class OMSWalletSession( signerKeyType = signerKeyType, expiresAt = expiresAt, auth = auth, + pendingWalletSelectionId = pendingWalletSelectionId, + pendingWalletType = walletType, ) } @@ -137,6 +142,7 @@ internal class OMSWalletSession( fun markAuthVerified( expiresAt: String, auth: OMSWalletSessionAuth, + walletType: technology.polygon.omswallet.models.WalletType, requiredRevision: Long? = null, ): Pair { synchronized(lock) { @@ -154,6 +160,7 @@ internal class OMSWalletSession( expiresAt = expiresAt, auth = auth, pendingWalletSelectionId = pendingWalletSelectionId, + walletType = walletType, ), ) return pendingWalletSelectionId to revision @@ -313,6 +320,7 @@ internal class OMSWalletSession( expiresAt = snapshot.expiresAt.orEmpty(), auth = auth, pendingWalletSelectionId = null, + walletType = snapshot.pendingWalletType, ) } diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/AttestationVerifier.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/AttestationVerifier.kt new file mode 100644 index 0000000..f56652d --- /dev/null +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/AttestationVerifier.kt @@ -0,0 +1,183 @@ +package technology.polygon.omswallet.wallet + +import com.upokecenter.cbor.CBORObject +import com.upokecenter.cbor.CBORType +import technology.polygon.omswallet.OMSWalletAttestationException +import java.io.ByteArrayInputStream +import java.security.MessageDigest +import java.security.Signature +import java.security.cert.CertPathValidator +import java.security.cert.CertificateFactory +import java.security.cert.PKIXParameters +import java.security.cert.TrustAnchor +import java.security.cert.X509Certificate +import kotlin.math.abs + +internal object AttestationVerifier { + private const val awsNitroRootSha256 = "641a0321a3e244efe456463195d606317ed7cdcc3c1756e09893f3c68f79bb5b" + private const val maxAgeMillis = 5 * 60 * 1_000L + + fun verify( + encodedDocument: String, + method: String, + path: String, + requestBody: String, + responseBody: String, + nonce: String, + trustedPcr0s: Set, + nowMillis: Long = System.currentTimeMillis(), + ) { + try { + val documentBytes = WalletImportBase64.decodeCanonical(encodedDocument, "attestation document") + val decoded = CBORObject.DecodeFromBytes(documentBytes) + require(decoded.HasOneTag(18)) { "WaaS attestation has an invalid COSE_Sign1 structure" } + val cose = decoded.UntagOne() + require(cose.type == CBORType.Array && cose.size() == 4) { + "WaaS attestation has an invalid COSE_Sign1 structure" + } + val protectedHeader = cose[0].requiredBytes("WaaS attestation has an invalid COSE_Sign1 structure") + require(cose[1].type == CBORType.Map && cose[1].size() == 0) { + "WaaS attestation has an invalid COSE_Sign1 structure" + } + val payload = cose[2].requiredBytes("WaaS attestation has an invalid COSE_Sign1 structure") + val signature = cose[3].requiredBytes("WaaS attestation has an invalid COSE_Sign1 structure") + require(signature.size == 96) { "WaaS attestation has an invalid COSE_Sign1 structure" } + + val protected = CBORObject.DecodeFromBytes(protectedHeader) + require( + protected.type == CBORType.Map && + protected[CBORObject.FromObject(1)]?.AsInt32Value() == -35, + ) { "WaaS attestation does not use COSE ES384" } + + val fields = CBORObject.DecodeFromBytes(payload) + require(fields.type == CBORType.Map) { "WaaS attestation payload is not a CBOR map" } + require(fields["digest"]?.AsString() == "SHA384") { "WaaS attestation payload is missing required fields" } + val timestamp = + fields["timestamp"]?.takeIf { it.type == CBORType.Integer }?.AsInt64Value() + ?: error("WaaS attestation payload is missing required fields") + val pcrs = + fields["pcrs"]?.takeIf { it.type == CBORType.Map } + ?: error("WaaS attestation payload is missing required fields") + val certificate = + fields["certificate"]?.requiredBytes("WaaS attestation payload is missing required fields") + ?: error("WaaS attestation payload is missing required fields") + val cabundle = + fields["cabundle"]?.takeIf { it.type == CBORType.Array } + ?: error("WaaS attestation payload is missing required fields") + val userData = + fields["user_data"]?.requiredBytes("WaaS attestation payload is missing required fields") + ?: error("WaaS attestation payload is missing required fields") + val documentNonce = + fields["nonce"]?.requiredBytes("WaaS attestation payload is missing required fields") + ?: error("WaaS attestation payload is missing required fields") + + require(abs(timestamp - nowMillis) <= maxAgeMillis) { + "WaaS attestation timestamp is outside the accepted freshness window" + } + require(pcrs.size() in 1..32) { "WaaS attestation contains an invalid PCR measurement" } + pcrs.entries.forEach { entry -> + val index = entry.key.takeIf { it.type == CBORType.Integer }?.AsInt32Value() + val measurement = entry.value.takeIf { it.type == CBORType.ByteString }?.GetByteString() + require(index != null && index in 0..31 && measurement?.size in setOf(32, 48, 64)) { + "WaaS attestation contains an invalid PCR measurement" + } + } + val pcr0 = pcrs[CBORObject.FromObject(0)]?.requiredBytes("WaaS attestation PCR0 is not trusted") + require(pcr0 != null && pcr0.toHex() in trustedPcr0s) { "WaaS attestation PCR0 is not trusted" } + require(documentNonce.contentEquals(nonce.toByteArray(Charsets.UTF_8))) { + "WaaS attestation nonce does not match the request" + } + val preimage = "${method.uppercase()} $path\n$requestBody\n$responseBody" + val hash = WalletImportBase64.encode(MessageDigest.getInstance("SHA-256").digest(preimage.toByteArray())) + require(userData.contentEquals("Sequence/1:$hash".toByteArray(Charsets.UTF_8))) { + "WaaS attestation is not bound to the request and response" + } + + require(cabundle.size() > 0) { "WaaS attestation certificate bundle is invalid" } + val authorities = (0 until cabundle.size()).map { cabundle[it].requiredBytes("WaaS attestation certificate bundle is invalid") } + val leaf = verifyCertificateChain(certificate, authorities, nowMillis) + + val signatureInput = + CBORObject + .NewArray() + .apply { + Add("Signature1") + Add(protectedHeader) + Add(byteArrayOf()) + Add(payload) + }.EncodeToBytes() + val verifier = Signature.getInstance("SHA384withECDSA") + verifier.initVerify(leaf.publicKey) + verifier.update(signatureInput) + require(verifier.verify(rawEcdsaSignatureToDer(signature))) { "WaaS attestation signature is invalid" } + } catch (exception: OMSWalletAttestationException) { + throw exception + } catch (exception: Exception) { + throw OMSWalletAttestationException(message = exception.message ?: "WaaS attestation verification failed", cause = exception) + } + } + + private fun verifyCertificateChain( + leafBytes: ByteArray, + authoritiesBytes: List, + nowMillis: Long, + ): X509Certificate { + require(authoritiesBytes.first().sha256Hex() == awsNitroRootSha256) { + "WaaS attestation certificate chain does not use the AWS Nitro root" + } + val factory = CertificateFactory.getInstance("X.509") + + fun certificate(bytes: ByteArray): X509Certificate = factory.generateCertificate(ByteArrayInputStream(bytes)) as X509Certificate + + val leaf = certificate(leafBytes) + val authorities = authoritiesBytes.map(::certificate) + val chain = listOf(leaf) + authorities.drop(1) + val parameters = + PKIXParameters(setOf(TrustAnchor(authorities.first(), null))).apply { + isRevocationEnabled = false + date = java.util.Date(nowMillis) + } + CertPathValidator.getInstance("PKIX").validate(factory.generateCertPath(chain), parameters) + + (listOf(leaf) + authorities).forEachIndexed { index, certificate -> + certificate.checkValidity(java.util.Date(nowMillis)) + val usages = certificate.keyUsage + if (index == 0) { + require(certificate.basicConstraints < 0 && usages?.getOrNull(0) == true) { + "WaaS attestation leaf certificate has invalid constraints" + } + } else { + require(certificate.basicConstraints >= index - 1 && usages?.getOrNull(5) == true) { + "WaaS attestation CA certificate has invalid constraints" + } + } + } + return leaf + } + + private fun rawEcdsaSignatureToDer(signature: ByteArray): ByteArray { + require(signature.size == 96) { "WaaS attestation signature is invalid" } + + fun integer(value: ByteArray): ByteArray { + var bytes = value.dropWhile { it == 0.toByte() }.toByteArray() + if (bytes.isEmpty()) bytes = byteArrayOf(0) + if (bytes[0].toInt() and 0x80 != 0) bytes = byteArrayOf(0) + bytes + return byteArrayOf(0x02) + derLength(bytes.size) + bytes + } + val r = integer(signature.copyOfRange(0, 48)) + val s = integer(signature.copyOfRange(48, 96)) + return byteArrayOf(0x30) + derLength(r.size + s.size) + r + s + } + + private fun derLength(length: Int): ByteArray = + if (length < 128) byteArrayOf(length.toByte()) else byteArrayOf(0x81.toByte(), length.toByte()) + + private fun CBORObject.requiredBytes(message: String): ByteArray { + require(type == CBORType.ByteString) { message } + return GetByteString() + } + + private fun ByteArray.sha256Hex(): String = MessageDigest.getInstance("SHA-256").digest(this).toHex() + + private fun ByteArray.toHex(): String = joinToString("") { "%02x".format(it) } +} diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletAuthResult.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletAuthResult.kt index 4fe3401..4a8e9d6 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletAuthResult.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletAuthResult.kt @@ -4,8 +4,8 @@ import kotlinx.coroutines.sync.Mutex import technology.polygon.omswallet.OMSWalletErrorCode import technology.polygon.omswallet.OMSWalletOperation import technology.polygon.omswallet.OMSWalletSelectionException -import technology.polygon.omswallet.models.CredentialInfo import technology.polygon.omswallet.models.Wallet +import technology.polygon.omswallet.models.WalletCredential import technology.polygon.omswallet.models.WalletType import technology.polygon.omswallet.runOMSWalletOperation @@ -42,7 +42,7 @@ enum class WalletSelectionBehavior { class PendingWalletSelection internal constructor( val walletType: WalletType, val wallets: List, - val credential: CredentialInfo, + val credential: WalletCredential, private val selectWalletAction: suspend (String) -> WalletSelectionResult, private val createAndSelectWalletAction: suspend (String?) -> WalletSelectionResult, ) { @@ -106,7 +106,7 @@ sealed interface CompleteAuthResult { val walletAddress: String, val wallet: Wallet, val wallets: List, - val credential: CredentialInfo, + val credential: WalletCredential, ) : CompleteAuthResult data class WalletSelection( diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt index 4046416..6ccab54 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt @@ -15,6 +15,7 @@ import technology.polygon.omswallet.OMSWalletErrorCode import technology.polygon.omswallet.OMSWalletOidcSessionAuth import technology.polygon.omswallet.OMSWalletOidcSessionAuthFlow import technology.polygon.omswallet.OMSWalletOperation +import technology.polygon.omswallet.OMSWalletResponseException import technology.polygon.omswallet.OMSWalletSelectionException import technology.polygon.omswallet.OMSWalletSessionAuth import technology.polygon.omswallet.OMSWalletSessionException @@ -23,28 +24,47 @@ import technology.polygon.omswallet.OMSWalletSessionState import technology.polygon.omswallet.OMSWalletStorageException import technology.polygon.omswallet.OMSWalletTransactionException import technology.polygon.omswallet.OMSWalletValidationException +import technology.polygon.omswallet.SolanaNetwork +import technology.polygon.omswallet.WalletImportConfiguration import technology.polygon.omswallet.indexer.IndexerClient import technology.polygon.omswallet.internal.generated.waas.AuthMode +import technology.polygon.omswallet.internal.generated.waas.AuthorizeRemoteAccessRequest +import technology.polygon.omswallet.internal.generated.waas.Ciphersuite import technology.polygon.omswallet.internal.generated.waas.CommitVerifierRequest import technology.polygon.omswallet.internal.generated.waas.CompleteAuthRequest import technology.polygon.omswallet.internal.generated.waas.CompleteAuthResponse import technology.polygon.omswallet.internal.generated.waas.CreateWalletRequest +import technology.polygon.omswallet.internal.generated.waas.ERC20TransferGrant import technology.polygon.omswallet.internal.generated.waas.ExecuteRequest import technology.polygon.omswallet.internal.generated.waas.GetIDTokenRequest +import technology.polygon.omswallet.internal.generated.waas.GetRecipientKeyRequest +import technology.polygon.omswallet.internal.generated.waas.GetSessionRequest +import technology.polygon.omswallet.internal.generated.waas.GetSessionUsageRequest +import technology.polygon.omswallet.internal.generated.waas.Grant +import technology.polygon.omswallet.internal.generated.waas.GrantKind +import technology.polygon.omswallet.internal.generated.waas.Grants +import technology.polygon.omswallet.internal.generated.waas.HPKEPayload import technology.polygon.omswallet.internal.generated.waas.Identity import technology.polygon.omswallet.internal.generated.waas.IdentityType +import technology.polygon.omswallet.internal.generated.waas.ImportWalletRequest +import technology.polygon.omswallet.internal.generated.waas.InspectCredentialRequest import technology.polygon.omswallet.internal.generated.waas.IsValidMessageSignatureRequest import technology.polygon.omswallet.internal.generated.waas.IsValidTypedDataSignatureRequest +import technology.polygon.omswallet.internal.generated.waas.KeyFormat import technology.polygon.omswallet.internal.generated.waas.LambdaWebRpcTransport import technology.polygon.omswallet.internal.generated.waas.ListAccessRequest import technology.polygon.omswallet.internal.generated.waas.ListWalletsRequest +import technology.polygon.omswallet.internal.generated.waas.NativeTransferGrant import technology.polygon.omswallet.internal.generated.waas.PrepareEthereumContractCallRequest import technology.polygon.omswallet.internal.generated.waas.PrepareEthereumTransactionRequest import technology.polygon.omswallet.internal.generated.waas.PrepareResponse +import technology.polygon.omswallet.internal.generated.waas.PrepareSolanaTransferRequest import technology.polygon.omswallet.internal.generated.waas.RevokeAccessRequest import technology.polygon.omswallet.internal.generated.waas.SignMessageRequest import technology.polygon.omswallet.internal.generated.waas.SignTypedDataRequest +import technology.polygon.omswallet.internal.generated.waas.SolanaRecipient import technology.polygon.omswallet.internal.generated.waas.TransactionStatusRequest +import technology.polygon.omswallet.internal.generated.waas.TransportPurpose import technology.polygon.omswallet.internal.generated.waas.UseWalletRequest import technology.polygon.omswallet.internal.generated.waas.WEBRPC_SCHEMA_VERSION import technology.polygon.omswallet.internal.generated.waas.WaasApi @@ -52,16 +72,23 @@ import technology.polygon.omswallet.internal.generated.waas.WaasClient import technology.polygon.omswallet.internal.generated.waas.WaasPublicClient import technology.polygon.omswallet.internal.generated.waas.WebRpcHttpResponse import technology.polygon.omswallet.models.AbiArg -import technology.polygon.omswallet.models.CredentialInfo +import technology.polygon.omswallet.models.AccessGrant +import technology.polygon.omswallet.models.AccessGrantPage +import technology.polygon.omswallet.models.AccessGrantType +import technology.polygon.omswallet.models.AuthorizedRemoteAccess +import technology.polygon.omswallet.models.EncryptedWalletImportKeyMaterial import technology.polygon.omswallet.models.FeeOption import technology.polygon.omswallet.models.FeeOptionSelection import technology.polygon.omswallet.models.FeeOptionSelector import technology.polygon.omswallet.models.FeeOptionWithBalance import technology.polygon.omswallet.models.FeeToken -import technology.polygon.omswallet.models.ListAccessResponse import technology.polygon.omswallet.models.Page +import technology.polygon.omswallet.models.RemoteAccessSession +import technology.polygon.omswallet.models.RemoteCredentialMetadata import technology.polygon.omswallet.models.SendTransactionRequest import technology.polygon.omswallet.models.SendTransactionResponse +import technology.polygon.omswallet.models.SmartSessionGrant +import technology.polygon.omswallet.models.SmartSessionGrantUsage import technology.polygon.omswallet.models.TokenBalance import technology.polygon.omswallet.models.TransactionMode import technology.polygon.omswallet.models.TransactionStatus @@ -69,6 +96,11 @@ import technology.polygon.omswallet.models.TransactionStatusPollingOptions import technology.polygon.omswallet.models.TransactionStatusResolution import technology.polygon.omswallet.models.TransactionStatusResponse import technology.polygon.omswallet.models.Wallet +import technology.polygon.omswallet.models.WalletCredential +import technology.polygon.omswallet.models.WalletImportCipherSuite +import technology.polygon.omswallet.models.WalletImportPrivateKey +import technology.polygon.omswallet.models.WalletImportRecipientKey +import technology.polygon.omswallet.models.WalletKeyOrigin import technology.polygon.omswallet.models.WalletType import technology.polygon.omswallet.network.OMSWalletEnvironment import technology.polygon.omswallet.network.OMSWalletHttpClient @@ -82,15 +114,22 @@ import technology.polygon.omswallet.utils.OMSWalletIsoTimestamps import technology.polygon.omswallet.utils.OMSWalletTimestamps import technology.polygon.omswallet.utils.formatUnits import java.math.BigInteger +import java.security.SecureRandom import java.util.Timer import java.util.TimerTask import technology.polygon.omswallet.internal.generated.waas.AbiArg as WaasAbiArg import technology.polygon.omswallet.internal.generated.waas.CredentialInfo as WaasCredentialInfo +import technology.polygon.omswallet.internal.generated.waas.CredentialMetadata as WaasCredentialMetadata +import technology.polygon.omswallet.internal.generated.waas.CredentialType as WaasCredentialType import technology.polygon.omswallet.internal.generated.waas.FeeOption as WaasFeeOption import technology.polygon.omswallet.internal.generated.waas.FeeOptionSelection as WaasFeeOptionSelection import technology.polygon.omswallet.internal.generated.waas.FeeToken as WaasFeeToken +import technology.polygon.omswallet.internal.generated.waas.GrantUsage as WaasGrantUsage +import technology.polygon.omswallet.internal.generated.waas.KeyOrigin as WaasKeyOrigin import technology.polygon.omswallet.internal.generated.waas.ListAccessResponse as WaasListAccessResponse +import technology.polygon.omswallet.internal.generated.waas.NetworkFamily as WaasNetworkFamily import technology.polygon.omswallet.internal.generated.waas.Page as WaasPage +import technology.polygon.omswallet.internal.generated.waas.SessionInfo as WaasSessionInfo import technology.polygon.omswallet.internal.generated.waas.TransactionMode as WaasTransactionMode import technology.polygon.omswallet.internal.generated.waas.TransactionStatus as WaasTransactionStatus import technology.polygon.omswallet.internal.generated.waas.TransactionStatusResponse as WaasTransactionStatusResponse @@ -158,6 +197,7 @@ class WalletClient private constructor( private val transactionStatusPollIntervalMillis: Long, private val transactionStatusPollTimeoutMillis: Long, private val transactionStatusDelay: suspend (Long) -> Unit, + private val walletImport: WalletImportConfiguration?, ) { companion object { /** @@ -190,6 +230,7 @@ class WalletClient private constructor( sessionExpiryDispatcher: SessionExpiryDispatcher = AndroidMainThreadSessionExpiryDispatcher, now: () -> Long = OMSWalletTimestamps::nowMilliseconds, projectScopeKey: String? = null, + walletImport: WalletImportConfiguration? = null, ): WalletClient { val createRuntime = { WalletScopeRuntime( @@ -218,6 +259,7 @@ class WalletClient private constructor( transactionStatusPollIntervalMillis = transactionStatusPollIntervalMillis, transactionStatusPollTimeoutMillis = transactionStatusPollTimeoutMillis, transactionStatusDelay = transactionStatusDelay, + walletImport = walletImport, ) } } @@ -232,6 +274,7 @@ class WalletClient private constructor( environment = environment, transport = transport, authorizeSignedRequest = ::authorizeSignedRequest, + walletImport = walletImport, ) private val indexerClient: IndexerClient = IndexerClient.create( @@ -1155,6 +1198,118 @@ class WalletClient private constructor( ) } + /** Imports and activates an Ethereum or Solana private key through the attested import transport. */ + suspend fun importWallet( + privateKey: WalletImportPrivateKey, + reference: String? = null, + ): WalletSelectionResult = + runOMSWalletOperation(OMSWalletOperation.WalletImportWallet) { + val context = walletImportActivationContext(privateKey.walletType) + WalletImportCrypto.validateReference(reference) + val plaintext = WalletImportCrypto.plaintext(privateKey) + try { + val recipient = + gateway.getWalletImportRecipientKey( + WalletImportCipherSuite.P256Sha256Aes256Gcm, + context.revision, + ) + val sealed = + WalletImportCrypto.sealP256Aes256Gcm( + WalletImportBase64.decodeCanonical(recipient.publicKey, "recipient publicKey"), + plaintext, + ) + val wallet = + gateway.importWallet( + walletType = privateKey.walletType, + keyMaterial = + EncryptedWalletImportKeyMaterial( + keyId = recipient.keyId, + cipherSuite = recipient.cipherSuite, + encapsulatedKey = WalletImportBase64.encode(sealed.first), + ciphertext = WalletImportBase64.encode(sealed.second), + ), + reference = reference, + requiredSessionRevision = context.revision, + ) + activateImportedWallet(wallet, context) + } finally { + plaintext.fill(0) + } + } + + /** Fetches an attested recipient key for caller-managed wallet-import encryption. */ + suspend fun getWalletImportRecipientKey(cipherSuite: WalletImportCipherSuite): WalletImportRecipientKey = + runOMSWalletOperation(OMSWalletOperation.WalletGetImportRecipientKey) { + val revision = requireWalletSelectionOrActiveSession() + gateway.getWalletImportRecipientKey(cipherSuite, revision).also { + requireWalletSelectionOrActiveSession(revision) + } + } + + /** Imports and activates caller-encrypted private-key material. */ + suspend fun importEncryptedWallet( + walletType: WalletType, + keyMaterial: EncryptedWalletImportKeyMaterial, + reference: String? = null, + ): WalletSelectionResult = + runOMSWalletOperation(OMSWalletOperation.WalletImportEncryptedWallet) { + val context = walletImportActivationContext(walletType) + WalletImportCrypto.validateReference(reference) + val wallet = gateway.importWallet(walletType, keyMaterial, reference, context.revision) + activateImportedWallet(wallet, context) + } + + private fun walletImportActivationContext(walletType: WalletType): WalletImportActivationContext = + synchronized(runtime.lifecycleLock) { + val revision = requireWalletSelectionOrActiveSession() + val snapshot = walletSession.requireSnapshot(revision) + val pendingId = snapshot.pendingWalletSelectionId + if (pendingId != null) { + require(snapshot.pendingWalletType == walletType) { + "Pending wallet selection requires a ${snapshot.pendingWalletType?.wireValue} wallet" + } + WalletImportActivationContext.Pending( + id = pendingId, + signerAddress = requireNotNull(snapshot.signerAddress), + signerKeyType = snapshot.signerKeyType, + revision = revision, + ) + } else { + WalletImportActivationContext.Active( + walletId = + snapshot.walletId?.takeIf(String::isNotBlank) + ?: throw OMSWalletSessionException(message = "No wallet selected"), + revision = revision, + ) + } + } + + private fun activateImportedWallet( + wallet: Wallet, + context: WalletImportActivationContext, + ): WalletSelectionResult = + synchronized(runtime.lifecycleLock) { + walletSession.requireRevision(context.revision) + val selectedRevision = + when (context) { + is WalletImportActivationContext.Active -> { + check(walletSession.requireSnapshot().walletId == context.walletId) { "Active wallet session changed" } + walletSession.selectWallet(wallet.id, wallet.address, context.revision) + } + + is WalletImportActivationContext.Pending -> { + walletSession.selectWalletForPendingSelection( + pendingWalletSelectionId = context.id, + signerAddress = context.signerAddress, + signerKeyType = context.signerKeyType, + walletId = wallet.id, + walletAddress = wallet.address, + ) + } + } + persistSelectedWallet(wallet, selectedRevision) + } + private suspend fun createWalletForCurrentSession( walletType: WalletType, reference: String?, @@ -1337,6 +1492,7 @@ class WalletClient private constructor( .markAuthVerified( expiresAt = completeAuth.credential.expiresAt, auth = sessionAuth, + walletType = walletType, requiredRevision = requiredSessionRevision, ).also { (_, revision) -> onSessionRevisionChanged?.invoke(revision) } } @@ -1484,6 +1640,9 @@ class WalletClient private constructor( ): String = runOMSWalletOperation(OMSWalletOperation.WalletSignMessage) { val activeSession = requireActiveWalletSession(OMSWalletOperation.WalletSignMessage) + require(activeSession.walletAddress.isEthereumAddress()) { + "An active Ethereum wallet is required" + } gateway.signMessage( walletId = activeSession.walletId, network = network, @@ -1492,6 +1651,20 @@ class WalletClient private constructor( ) } + /** Signs [message] with the currently selected Solana wallet. */ + suspend fun signSolanaMessage(message: String): String = + runOMSWalletOperation(OMSWalletOperation.WalletSignSolanaMessage) { + val activeSession = requireActiveWalletSession(OMSWalletOperation.WalletSignSolanaMessage) + require(!activeSession.walletAddress.isEthereumAddress()) { + "An active Solana wallet is required" + } + gateway.signSolanaMessage( + walletId = activeSession.walletId, + message = message, + requiredSessionRevision = activeSession.revision, + ) + } + /** * Signs EIP-712 [typedData] with the currently selected wallet on [network]. */ @@ -1501,6 +1674,9 @@ class WalletClient private constructor( ): String = runOMSWalletOperation(OMSWalletOperation.WalletSignTypedData) { val activeSession = requireActiveWalletSession(OMSWalletOperation.WalletSignTypedData) + require(activeSession.walletAddress.isEthereumAddress()) { + "An active Ethereum wallet is required" + } gateway.signTypedData( walletId = activeSession.walletId, network = network, @@ -1531,6 +1707,24 @@ class WalletClient private constructor( ) } + /** Validates [signature] for a Solana [message] through the WaaS public wallet RPC. */ + suspend fun isValidSolanaMessageSignature( + message: String, + signature: String, + ): Boolean = + runOMSWalletOperation(OMSWalletOperation.WalletIsValidSolanaMessageSignature) { + val activeSession = + requireActiveWalletSession( + OMSWalletOperation.WalletIsValidSolanaMessageSignature, + requireCredential = false, + ) + gateway.isValidSolanaMessageSignature( + walletId = activeSession.walletId, + message = message, + signature = signature, + ) + } + /** * Validates [signature] for EIP-712 [typedData] through the WaaS public wallet RPC. */ @@ -1594,6 +1788,9 @@ class WalletClient private constructor( ): SendTransactionResponse = runOMSWalletOperation(OMSWalletOperation.WalletSendTransaction) { val activeSession = requireActiveWalletSession(OMSWalletOperation.WalletSendTransaction) + require(activeSession.walletAddress.isEthereumAddress()) { + "An active Ethereum wallet is required" + } require(request.value.signum() >= 0) { "Transaction value must be non-negative" } val prepared = gateway.prepareEthereumTransaction( @@ -1613,6 +1810,44 @@ class WalletClient private constructor( ) } + /** Sends a native SOL or SPL token transfer from the selected Solana wallet. */ + suspend fun sendSolanaTransfer( + network: SolanaNetwork, + asset: String, + to: String, + amount: BigInteger, + mode: TransactionMode = TransactionMode.Relayer, + waitForStatus: Boolean = true, + statusPolling: TransactionStatusPollingOptions? = null, + selectFeeOption: FeeOptionSelector? = null, + ): SendTransactionResponse = + runOMSWalletOperation(OMSWalletOperation.WalletSendSolanaTransfer) { + val activeSession = requireActiveWalletSession(OMSWalletOperation.WalletSendSolanaTransfer) + require(!activeSession.walletAddress.isEthereumAddress()) { + "An active Solana wallet is required" + } + require(amount.signum() >= 0) { "Transfer amount must be non-negative" } + val prepared = + gateway.prepareSolanaTransfer( + walletId = activeSession.walletId, + network = network, + asset = asset, + to = to, + amount = amount, + mode = mode, + requiredSessionRevision = activeSession.revision, + ) + executePreparedTransaction( + network = null, + walletAddress = null, + prepared = prepared, + requiredSessionRevision = activeSession.revision, + selectFeeOption = selectFeeOption, + waitForStatus = waitForStatus, + statusPolling = statusPolling, + ) + } + /** * Calls a state-changing smart contract function through the WaaS * prepare/execute flow. @@ -1629,6 +1864,9 @@ class WalletClient private constructor( ): SendTransactionResponse = runOMSWalletOperation(OMSWalletOperation.WalletCallContract) { val activeSession = requireActiveWalletSession(OMSWalletOperation.WalletCallContract) + require(activeSession.walletAddress.isEthereumAddress()) { + "An active Ethereum wallet is required" + } val prepared = gateway.prepareEthereumContractCall( walletId = activeSession.walletId, @@ -1660,26 +1898,65 @@ class WalletClient private constructor( gateway.transactionStatus(txnId, activeSession.revision) } - /** - * Returns all credentials that currently have access to the selected wallet. - * - * When [pageSize] is provided, the SDK follows WaaS cursors using that page - * size and returns the combined credential list. - */ - suspend fun listAccess(pageSize: UInt? = null): List = + /** Returns display metadata for a remote credential before the owner approves access. */ + suspend fun inspectRemoteCredential(credentialId: String): RemoteCredentialMetadata = + runOMSWalletOperation(OMSWalletOperation.WalletInspectRemoteCredential) { + require(credentialId.isNotBlank()) { "credentialId is required" } + gateway.inspectRemoteCredential(projectId, credentialId) + } + + /** Authorizes owner-approved EVM smart-session grants for a remote credential. */ + suspend fun authorizeRemoteAccess( + credentialId: String, + network: Network, + grants: List, + expiresAt: String, + sessionId: String? = null, + ): AuthorizedRemoteAccess = + runOMSWalletOperation(OMSWalletOperation.WalletAuthorizeRemoteAccess) { + val activeSession = requireActiveWalletSession(OMSWalletOperation.WalletAuthorizeRemoteAccess) + require(activeSession.walletAddress.isEthereumAddress()) { + "An active Ethereum wallet is required" + } + require(grants.isNotEmpty()) { "At least one grant is required" } + val result = + gateway.authorizeRemoteAccess( + walletId = activeSession.walletId, + credentialId = credentialId, + network = network, + grants = grants, + expiresAt = expiresAt, + sessionId = sessionId, + requiredSessionRevision = activeSession.revision, + ) + val current = requireActiveWalletSession(OMSWalletOperation.WalletAuthorizeRemoteAccess) + check(current.walletId == activeSession.walletId && current.revision == activeSession.revision) { + "Active wallet session changed" + } + result + } + + /** Returns all access grants, following WaaS cursors with the requested [pageSize]. */ + suspend fun listAccess( + pageSize: UInt? = null, + type: AccessGrantType? = null, + ): List = runOMSWalletOperation(OMSWalletOperation.WalletListAccess) { - val credentials = mutableListOf() - listAccessPages(pageSize = pageSize).collect { response -> - credentials += response.credentials + val grants = mutableListOf() + listAccessPages(pageSize = pageSize, type = type).collect { response -> + grants += response.grants } - credentials + grants } /** * Emits credential-access pages for the selected wallet until WaaS stops * returning a cursor. */ - fun listAccessPages(pageSize: UInt? = null): Flow = + fun listAccessPages( + pageSize: UInt? = null, + type: AccessGrantType? = null, + ): Flow = flow { val activeSession = requireActiveWalletSession(OMSWalletOperation.WalletListAccessPages) var cursor: String? = null @@ -1689,6 +1966,7 @@ class WalletClient private constructor( requestListAccessPage( pageSize = pageSize, cursor = cursor, + type = type, activeSession = activeSession, ) } @@ -1703,11 +1981,13 @@ class WalletClient private constructor( suspend fun listAccessPage( pageSize: UInt? = null, cursor: String? = null, - ): ListAccessResponse = + type: AccessGrantType? = null, + ): AccessGrantPage = runOMSWalletOperation(OMSWalletOperation.WalletListAccessPage) { requestListAccessPage( pageSize, cursor, + type, requireActiveWalletSession(OMSWalletOperation.WalletListAccessPage), ) } @@ -1715,14 +1995,43 @@ class WalletClient private constructor( private suspend fun requestListAccessPage( pageSize: UInt?, cursor: String?, + type: AccessGrantType?, activeSession: ActiveWalletSession, - ): ListAccessResponse = + ): AccessGrantPage = gateway.listAccessPage( walletId = activeSession.walletId, page = accessPage(pageSize, cursor), + type = type, requiredSessionRevision = activeSession.revision, ) + /** Returns one owner-visible smart-session and checks that it belongs to the active wallet. */ + suspend fun getRemoteAccessSession(sessionId: String): RemoteAccessSession = + runOMSWalletOperation(OMSWalletOperation.WalletGetRemoteAccessSession) { + require(sessionId.isNotBlank()) { "sessionId is required" } + val activeSession = requireActiveWalletSession(OMSWalletOperation.WalletGetRemoteAccessSession) + val session = gateway.getRemoteAccessSession(sessionId, activeSession.revision) + require(session.walletId == activeSession.walletId) { + "Session does not belong to the active wallet" + } + session + } + + /** Returns grant usage for an owner-visible smart session on [network]. */ + suspend fun getRemoteAccessSessionUsage( + sessionId: String, + network: Network, + ): List = + runOMSWalletOperation(OMSWalletOperation.WalletGetRemoteAccessSessionUsage) { + require(sessionId.isNotBlank()) { "sessionId is required" } + val activeSession = requireActiveWalletSession(OMSWalletOperation.WalletGetRemoteAccessSessionUsage) + gateway.getRemoteAccessSessionUsage( + sessionId = sessionId, + network = network, + requiredSessionRevision = activeSession.revision, + ) + } + /** * Returns an ID token for the currently selected wallet. */ @@ -1745,12 +2054,16 @@ class WalletClient private constructor( * * Use [listAccess] or [listAccessPage] to find credential IDs. */ - suspend fun revokeAccess(targetCredentialId: String): Unit = + suspend fun revokeAccess( + credentialId: String, + sessionId: String? = null, + ): Unit = runOMSWalletOperation(OMSWalletOperation.WalletRevokeAccess) { val activeSession = requireActiveWalletSession(OMSWalletOperation.WalletRevokeAccess) gateway.revokeAccess( walletId = activeSession.walletId, - targetCredentialId = targetCredentialId, + targetCredentialId = credentialId, + sessionId = sessionId, requiredSessionRevision = activeSession.revision, ) } @@ -2056,8 +2369,8 @@ class WalletClient private constructor( } private suspend fun executePreparedTransaction( - network: Network, - walletAddress: String, + network: Network?, + walletAddress: String?, prepared: PreparedWalletTransaction, requiredSessionRevision: Long, selectFeeOption: FeeOptionSelector?, @@ -2086,13 +2399,22 @@ class WalletClient private constructor( } else -> { - selectFeeOption.select( - enrichFeeOptionsWithBalances( - network = network, - walletAddress = walletAddress, - feeOptions = prepared.feeOptions, - ), - ) ?: throw IllegalArgumentException( + val options = + if (network != null && walletAddress != null) { + enrichFeeOptionsWithBalances( + network = network, + walletAddress = walletAddress, + feeOptions = prepared.feeOptions, + ) + } else { + prepared.feeOptions.mapIndexed { index, feeOption -> + FeeOptionWithBalance( + feeOption = feeOption, + selection = FeeOptionSelection(feeOption, index.toUInt()), + ) + } + } + selectFeeOption.select(options) ?: throw IllegalArgumentException( "No fee option selected for unsponsored transaction", ) } @@ -2181,7 +2503,7 @@ class WalletClient private constructor( } } - return feeOptions.map { feeOption -> + return feeOptions.mapIndexed { index, feeOption -> val balance = if (feeOption.token.isNativeToken()) { nativeBalance @@ -2193,6 +2515,7 @@ class WalletClient private constructor( val decimals = feeOption.token.balanceDecimals() FeeOptionWithBalance( feeOption = feeOption, + selection = FeeOptionSelection(feeOption, index.toUInt()), balance = balance, available = balance?.balance?.formatTokenAmount(decimals), availableRaw = balance?.balance, @@ -2218,7 +2541,9 @@ class WalletClient private constructor( runCatching { formatUnits(BigInteger(this), scale) }.getOrDefault(this) } ?: this - private fun List.defaultSelection(): FeeOptionSelection = FeeOptionSelection(first()) + private fun List.defaultSelection(): FeeOptionSelection = FeeOptionSelection(first(), 0u) + + private fun String.isEthereumAddress(): Boolean = startsWith("0x") private suspend fun waitForTransactionStatus( txnId: String, @@ -2315,7 +2640,7 @@ private data class WalletAuthCompletion( val nextWalletsCursor: String?, val email: String?, val identity: Identity, - val credential: CredentialInfo, + val credential: WalletCredential, ) private data class WalletsPage( @@ -2335,6 +2660,22 @@ private data class ActiveWalletSession( val revision: Long, ) +private sealed interface WalletImportActivationContext { + val revision: Long + + data class Active( + val walletId: String, + override val revision: Long, + ) : WalletImportActivationContext + + data class Pending( + val id: Long, + val signerAddress: String, + val signerKeyType: WalletSigningAlgorithm?, + override val revision: Long, + ) : WalletImportActivationContext +} + private data class ExpiryNotification( val event: OMSWalletSessionExpiredEvent, val revision: Long, @@ -2361,6 +2702,7 @@ private class WaasWalletGateway( endpoint: String, body: String, ) -> String, + private val walletImport: WalletImportConfiguration?, ) { private val publicClient: WaasPublicClient = WaasPublicClient( @@ -2507,6 +2849,57 @@ private class WaasWalletGateway( ).wallet .toModel() + suspend fun getWalletImportRecipientKey( + cipherSuite: WalletImportCipherSuite, + requiredSessionRevision: Long, + ): WalletImportRecipientKey { + val response = + walletImportClient(requiredSessionRevision).getRecipientKey( + GetRecipientKeyRequest( + purpose = TransportPurpose.WalletImport, + suite = cipherSuite.toWaas(), + ), + ) + if (response.keyId.isBlank() || response.publicKey.isBlank()) { + throw OMSWalletResponseException(message = "Wallet import recipient-key response is incomplete") + } + WalletImportBase64.decodeCanonical(response.publicKey, "recipient publicKey") + return WalletImportRecipientKey(response.keyId, cipherSuite, response.publicKey) + } + + suspend fun importWallet( + walletType: WalletType, + keyMaterial: EncryptedWalletImportKeyMaterial, + reference: String?, + requiredSessionRevision: Long, + ): Wallet { + require(keyMaterial.keyId.isNotBlank()) { "keyMaterial.keyId is required" } + WalletImportCrypto.validateReference(reference) + WalletImportBase64.decodeCanonical(keyMaterial.encapsulatedKey, "keyMaterial.encapsulatedKey") + WalletImportBase64.decodeCanonical(keyMaterial.ciphertext, "keyMaterial.ciphertext") + val wallet = + walletImportClient(requiredSessionRevision) + .importWallet( + ImportWalletRequest( + networkFamily = walletType.toNetworkFamily(), + format = KeyFormat.PrivateKey, + keyMaterial = + HPKEPayload( + keyId = keyMaterial.keyId, + suite = keyMaterial.cipherSuite.toWaas(), + encapsulatedKey = keyMaterial.encapsulatedKey, + ciphertext = keyMaterial.ciphertext, + ), + reference = reference, + ), + ).wallet + .toModel() + if (wallet.type != walletType) { + throw OMSWalletResponseException(message = "Imported wallet network family does not match the request") + } + return wallet + } + suspend fun createWallet( walletType: WalletType, reference: String?, @@ -2515,7 +2908,7 @@ private class WaasWalletGateway( signedClient(requiredSessionRevision) .createWallet( CreateWalletRequest( - type = walletType.toWaas(), + networkFamily = walletType.toNetworkFamily(), reference = reference, ), ).wallet @@ -2552,6 +2945,20 @@ private class WaasWalletGateway( ), ).signature + suspend fun signSolanaMessage( + walletId: String, + message: String, + requiredSessionRevision: Long, + ): String = + signedClient(requiredSessionRevision) + .signMessage( + SignMessageRequest( + walletId = walletId, + network = "", + message = message, + ), + ).signature + suspend fun signTypedData( walletId: String, network: Network, @@ -2577,6 +2984,22 @@ private class WaasWalletGateway( .isValidMessageSignature( IsValidMessageSignatureRequest( network = network.id.toString(), + networkFamily = WaasNetworkFamily.EVM, + walletId = walletId, + message = message, + signature = signature, + ), + ).isValid + + suspend fun isValidSolanaMessageSignature( + walletId: String, + message: String, + signature: String, + ): Boolean = + publicClient + .isValidMessageSignature( + IsValidMessageSignatureRequest( + networkFamily = WaasNetworkFamily.Solana, walletId = walletId, message = message, signature = signature, @@ -2638,6 +3061,27 @@ private class WaasWalletGateway( ), ).toPreparedWalletTransaction() + suspend fun prepareSolanaTransfer( + walletId: String, + network: SolanaNetwork, + asset: String, + to: String, + amount: BigInteger, + mode: TransactionMode, + requiredSessionRevision: Long, + ): PreparedWalletTransaction = + signedClient(requiredSessionRevision) + .prepareSolanaTransfer( + PrepareSolanaTransferRequest( + walletId = walletId, + network = network.wireValue, + asset = asset, + recipient = SolanaRecipient(address = to), + amount = amount.toString(), + mode = mode.toWaas(), + ), + ).toPreparedWalletTransaction() + suspend fun execute( txnId: String, feeOption: FeeOptionSelection?, @@ -2663,19 +3107,85 @@ private class WaasWalletGateway( .transactionStatus(TransactionStatusRequest(txnId = txnId)) .toModel() + suspend fun inspectRemoteCredential( + scope: String, + credentialId: String, + ): RemoteCredentialMetadata = + publicClient + .inspectCredential( + InspectCredentialRequest( + scope = scope, + credentialId = credentialId, + ), + ).metadata + .toModel() + + suspend fun authorizeRemoteAccess( + walletId: String, + credentialId: String, + network: Network, + grants: List, + expiresAt: String, + sessionId: String?, + requiredSessionRevision: Long, + ): AuthorizedRemoteAccess { + val response = + signedClient(requiredSessionRevision) + .authorizeRemoteAccess( + AuthorizeRemoteAccessRequest( + credentialId = credentialId, + walletId = walletId, + grants = Grants(entries = grants.map { it.toWaas() }), + expiry = expiresAt, + chainId = network.id.toString(), + sessionId = sessionId, + ), + ) + return AuthorizedRemoteAccess( + walletId = walletId, + sessionId = response.sessionId, + expiresAt = response.expiry, + ) + } + suspend fun listAccessPage( walletId: String, page: Page?, + type: AccessGrantType?, requiredSessionRevision: Long, - ): ListAccessResponse = + ): AccessGrantPage = signedClient(requiredSessionRevision) .listAccess( ListAccessRequest( walletId = walletId, page = page?.toWaas(), + type = type?.toWaas(), ), ).toModel() + suspend fun getRemoteAccessSession( + sessionId: String, + requiredSessionRevision: Long, + ): RemoteAccessSession = + signedClient(requiredSessionRevision) + .getSession(GetSessionRequest(sessionId = sessionId)) + .session + .toModel() + + suspend fun getRemoteAccessSessionUsage( + sessionId: String, + network: Network, + requiredSessionRevision: Long, + ): List = + signedClient(requiredSessionRevision) + .getSessionUsage( + GetSessionUsageRequest( + sessionId = sessionId, + network = network.id.toString(), + ), + ).entries + .map { it.toModel() } + suspend fun getIdToken( walletId: String, ttlSeconds: UInt?, @@ -2694,12 +3204,14 @@ private class WaasWalletGateway( suspend fun revokeAccess( walletId: String, targetCredentialId: String, + sessionId: String?, requiredSessionRevision: Long, ) { signedClient(requiredSessionRevision).revokeAccess( RevokeAccessRequest( targetCredentialId = targetCredentialId, walletId = walletId, + sessionId = sessionId, ), ) } @@ -2713,6 +3225,55 @@ private class WaasWalletGateway( transport = signedTransport(requiredSessionRevision, allowCredentialCreation), ) + private fun walletImportClient(requiredSessionRevision: Long): WaasClient { + require(walletImport != null) { "Wallet import requires walletImport.trustedPcr0s configuration" } + return WaasClient( + baseUrl = environment.walletApiBaseUrl(), + transport = attestedSignedTransport(requiredSessionRevision, walletImport), + ) + } + + private fun attestedSignedTransport( + requiredSessionRevision: Long, + configuration: WalletImportConfiguration, + ): LambdaWebRpcTransport = + LambdaWebRpcTransport { baseUrl, path, body, headers -> + val endpoint = resolveEndpoint(path) + val requestPath = WaasApi.basePath + endpoint + val walletSignatureHeader = + withContext(Dispatchers.IO) { + authorizeSignedRequest(requiredSessionRevision, false, endpoint, body) + } + val nonceBytes = ByteArray(18).also(SecureRandom()::nextBytes) + val nonce = WalletImportBase64.encode(nonceBytes) + val requestHeaders = + defaultSignedHeaders(headers, walletSignatureHeader).toMutableMap().apply { + put("X-Attestation-Nonce", nonce) + } + val response = + transport.postJsonWithStatus( + baseUrl = baseUrl, + path = requestPath, + body = body, + headers = requestHeaders, + ) + val document = + response.headers["x-attestation-document"] + ?: throw technology.polygon.omswallet.OMSWalletAttestationException( + message = "WaaS response is missing its attestation document", + ) + AttestationVerifier.verify( + encodedDocument = document, + method = "POST", + path = requestPath, + requestBody = body, + responseBody = response.body, + nonce = nonce, + trustedPcr0s = configuration.trustedPcr0s, + ) + WebRpcHttpResponse(response.statusCode, response.body) + } + private fun signedTransport( requiredSessionRevision: Long, allowCredentialCreation: Boolean, @@ -2782,21 +3343,60 @@ private class WaasWalletGateway( nextWalletsCursor = page?.cursor?.takeIf { it.isNotBlank() }, email = email, identity = identity, - credential = credential.toModel(), + credential = credential.toWalletCredential(), ) private fun WalletType.toWaas(): WaasWalletType = when (this) { WalletType.Ethereum -> WaasWalletType.Ethereum + WalletType.Solana -> WaasWalletType.Solana WalletType.UNKNOWN_DEFAULT -> WaasWalletType.UNKNOWN_DEFAULT } + private fun WalletType.toNetworkFamily(): WaasNetworkFamily = + when (this) { + WalletType.Ethereum -> WaasNetworkFamily.EVM + WalletType.Solana -> WaasNetworkFamily.Solana + WalletType.UNKNOWN_DEFAULT -> WaasNetworkFamily.UNKNOWN_DEFAULT + } + + private fun WalletImportCipherSuite.toWaas(): Ciphersuite = + when (this) { + WalletImportCipherSuite.X25519Sha256Aes256Gcm -> Ciphersuite.X25519_SHA256_AES_256_GCM + WalletImportCipherSuite.X25519Sha256ChaCha20Poly1305 -> Ciphersuite.X25519_SHA256_ChaCha20_Poly1305 + WalletImportCipherSuite.P256Sha256Aes256Gcm -> Ciphersuite.P256_SHA256_AES_256_GCM + WalletImportCipherSuite.P256Sha256ChaCha20Poly1305 -> Ciphersuite.P256_SHA256_ChaCha20_Poly1305 + } + private fun WaasWalletType.toModel(): WalletType = when (this) { WaasWalletType.Ethereum -> WalletType.Ethereum + WaasWalletType.Solana -> WalletType.Solana WaasWalletType.UNKNOWN_DEFAULT -> WalletType.UNKNOWN_DEFAULT } + private fun WaasNetworkFamily.toWalletType(): WalletType = + when (this) { + WaasNetworkFamily.EVM -> WalletType.Ethereum + + WaasNetworkFamily.Solana -> WalletType.Solana + + WaasNetworkFamily.UNKNOWN_DEFAULT -> throw OMSWalletResponseException( + message = "Wallet response has an invalid networkFamily", + ) + } + + private fun WaasKeyOrigin.toModel(): WalletKeyOrigin = + when (this) { + WaasKeyOrigin.Enclave -> WalletKeyOrigin.Enclave + + WaasKeyOrigin.Imported -> WalletKeyOrigin.Imported + + WaasKeyOrigin.UNKNOWN_DEFAULT -> throw OMSWalletResponseException( + message = "Wallet response has an invalid keyOrigin", + ) + } + private fun TransactionMode.toWaas(): WaasTransactionMode = when (this) { TransactionMode.Native -> WaasTransactionMode.Native @@ -2816,9 +3416,16 @@ private class WaasWalletGateway( private fun WaasWallet.toModel(): Wallet = Wallet( id = id, - type = type.toModel(), + type = + networkFamily?.toWalletType() ?: throw OMSWalletResponseException( + message = "Wallet response is missing networkFamily", + ), address = address, reference = reference, + keyOrigin = + keyOrigin?.toModel() ?: throw OMSWalletResponseException( + message = "Wallet response is missing keyOrigin", + ), ) private fun WaasFeeToken.toModel(): FeeToken = @@ -2840,7 +3447,11 @@ private class WaasWalletGateway( displayValue = displayValue, ) - private fun FeeOptionSelection.toWaas(): WaasFeeOptionSelection = WaasFeeOptionSelection(token = token) + private fun FeeOptionSelection.toWaas(): WaasFeeOptionSelection = + WaasFeeOptionSelection( + token = token, + index = index, + ) private fun Page.toWaas(): WaasPage = WaasPage( @@ -2854,16 +3465,42 @@ private class WaasWalletGateway( value = value, ) - private fun WaasCredentialInfo.toModel(): CredentialInfo = - CredentialInfo( + private fun WaasCredentialInfo.toWalletCredential(): WalletCredential = + WalletCredential( credentialId = credentialId, expiresAt = expiresAt, isCaller = isCaller, ) - private fun WaasListAccessResponse.toModel(): ListAccessResponse = - ListAccessResponse( - credentials = credentials.map { it.toModel() }, + private fun WaasCredentialInfo.toModel(): AccessGrant = + when (type) { + WaasCredentialType.Direct -> { + AccessGrant.Direct(toWalletCredential()) + } + + WaasCredentialType.Remote -> { + AccessGrant.Remote( + credential = toWalletCredential(), + sessionId = + sessionId?.takeIf(String::isNotBlank) + ?: throw OMSWalletResponseException(message = "Remote access credential is missing sessionId"), + metadata = + metadata?.toModel() + ?: throw OMSWalletResponseException(message = "Remote access credential is missing metadata"), + grants = + grants?.entries?.map { it.toModel() } + ?: throw OMSWalletResponseException(message = "Remote access credential is missing grants"), + ) + } + + WaasCredentialType.UNKNOWN_DEFAULT -> { + throw OMSWalletResponseException(message = "Access response has an invalid credential type") + } + } + + private fun WaasListAccessResponse.toModel(): AccessGrantPage = + AccessGrantPage( + grants = credentials.map { it.toModel() }, page = page?.let { Page( @@ -2873,6 +3510,125 @@ private class WaasWalletGateway( }, ) + private fun AccessGrantType.toWaas(): WaasCredentialType = + when (this) { + AccessGrantType.Direct -> WaasCredentialType.Direct + AccessGrantType.Remote -> WaasCredentialType.Remote + } + + private fun WaasCredentialMetadata.toModel(): RemoteCredentialMetadata = + RemoteCredentialMetadata( + appUrl = appUrl, + appName = appName, + appLogoUrl = appLogoUrl, + custom = custom, + ) + + private fun SmartSessionGrant.toWaas(): Grant = + when (this) { + is SmartSessionGrant.NativeTransfer -> { + require(to.isEthereumAddressValue()) { "Invalid native transfer recipient" } + require(limit.signum() >= 0) { "Native transfer limit must be non-negative" } + Grant( + kind = GrantKind.NativeTransfer, + nativeTransfer = NativeTransferGrant(to = to, limit = limit.toString()), + ) + } + + is SmartSessionGrant.Erc20Transfer -> { + require(token.isEthereumAddressValue()) { "Invalid ERC-20 token address" } + require(to?.isEthereumAddressValue() != false) { "Invalid ERC-20 recipient" } + require(limit.signum() >= 0) { "ERC-20 transfer limit must be non-negative" } + Grant( + kind = GrantKind.ERC20Transfer, + erc20transfer = + ERC20TransferGrant( + token = token, + to = to, + limit = limit.toString(), + cumulative = cumulative, + ), + ) + } + } + + private fun Grant.toModel(): SmartSessionGrant = + when (kind) { + GrantKind.NativeTransfer -> { + val entry = + nativeTransfer + ?: throw OMSWalletResponseException(message = "Session contains an invalid native transfer grant") + requireResponseAddress(entry.to, "native transfer recipient") + SmartSessionGrant.NativeTransfer( + to = entry.to, + limit = entry.limit.toUnsignedBigInteger("native transfer limit"), + ) + } + + GrantKind.ERC20Transfer -> { + val entry = + erc20transfer + ?: throw OMSWalletResponseException(message = "Session contains an invalid ERC-20 transfer grant") + requireResponseAddress(entry.token, "ERC-20 token") + entry.to?.let { requireResponseAddress(it, "ERC-20 recipient") } + SmartSessionGrant.Erc20Transfer( + token = entry.token, + to = entry.to, + limit = entry.limit.toUnsignedBigInteger("ERC-20 transfer limit"), + cumulative = entry.cumulative, + ) + } + + GrantKind.UNKNOWN_DEFAULT -> { + throw OMSWalletResponseException(message = "Session contains an invalid grant") + } + } + + private fun WaasSessionInfo.toModel(): RemoteAccessSession { + val parsedChainId = chainId.toIntOrNull() + if (parsedChainId == null || parsedChainId <= 0 || parsedChainId.toString() != chainId) { + throw OMSWalletResponseException(message = "Session contains an invalid chain ID") + } + requireResponseAddress(signerAddress, "signer address") + if (sessionId.isBlank() || walletId.isBlank() || expiresAt.isBlank()) { + throw OMSWalletResponseException(message = "Session response is missing required fields") + } + return RemoteAccessSession( + sessionId = sessionId, + walletId = walletId, + signerAddress = signerAddress, + grants = grants.entries.map { it.toModel() }, + chainId = parsedChainId, + expiresAt = expiresAt, + ) + } + + private fun WaasGrantUsage.toModel(): SmartSessionGrantUsage = + SmartSessionGrantUsage( + grant = grant.toModel(), + used = used?.toUnsignedBigInteger("grant usage"), + ) + + private fun String.toUnsignedBigInteger(field: String): BigInteger { + val parsed = toBigIntegerOrNull() + if (parsed == null || parsed.signum() < 0 || parsed.toString() != this) { + throw OMSWalletResponseException(message = "Session contains an invalid $field") + } + return parsed + } + + private fun requireResponseAddress( + value: String, + field: String, + ) { + if (!value.isEthereumAddressValue()) { + throw OMSWalletResponseException(message = "Session contains an invalid $field") + } + } + + private fun String.isEthereumAddressValue(): Boolean = + length == 42 && startsWith("0x") && drop(2).all { it.digitToIntOrNull(16) != null } + private fun WaasTransactionStatusResponse.toModel(): TransactionStatusResponse = TransactionStatusResponse( status = status.toModel(), @@ -2895,6 +3651,7 @@ private class WaasWalletGateway( private fun String.toWalletType(): WalletType = when (this) { WalletType.Ethereum.wireValue -> WalletType.Ethereum + WalletType.Solana.wireValue -> WalletType.Solana else -> WalletType.UNKNOWN_DEFAULT } diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletImportCrypto.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletImportCrypto.kt new file mode 100644 index 0000000..7f2110a --- /dev/null +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletImportCrypto.kt @@ -0,0 +1,137 @@ +package technology.polygon.omswallet.wallet + +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo +import org.bouncycastle.crypto.hpke.HPKE +import technology.polygon.omswallet.models.WalletImportPrivateKey +import java.math.BigInteger + +internal object WalletImportCrypto { + private val secp256k1Order = BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16) + private const val base58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + + fun plaintext(privateKey: WalletImportPrivateKey): ByteArray = + when (privateKey) { + is WalletImportPrivateKey.Ethereum -> { + val value = privateKey.value.trimAsciiWhitespace() + val hex = value.removePrefix("0x") + require(hex.length == 64 && hex.all { it in '0'..'9' || it in 'a'..'f' || it in 'A'..'F' }) { + "Ethereum privateKey must be 32 bytes or 64 hexadecimal characters" + } + requireValidEthereumScalar(hex.hexBytes()) + value.toByteArray(Charsets.UTF_8) + } + + is WalletImportPrivateKey.EthereumBytes -> { + require(privateKey.value.size == 32) { "Ethereum privateKey must contain exactly 32 bytes" } + requireValidEthereumScalar(privateKey.value) + privateKey.value.copyOf() + } + + is WalletImportPrivateKey.Solana -> { + val value = privateKey.value.trimAsciiWhitespace() + val decoded = decodeBase58(value) + require(decoded.size == 32 || decoded.size == 64) { + "Solana privateKey must decode to a 32-byte seed or 64-byte keypair" + } + require(value.length != 32 && value.length != 64) { + "Solana privateKey string is ambiguous; provide the raw bytes instead" + } + value.toByteArray(Charsets.UTF_8) + } + + is WalletImportPrivateKey.SolanaBytes -> { + require(privateKey.value.size == 32 || privateKey.value.size == 64) { + "Solana privateKey must contain a 32-byte seed or 64-byte keypair" + } + privateKey.value.copyOf() + } + } + + fun validateReference(reference: String?) { + require(reference == null || reference.toByteArray(Charsets.UTF_8).size <= 128) { + "reference must be at most 128 UTF-8 bytes" + } + } + + fun sealP256Aes256Gcm( + recipientPublicKey: ByteArray, + plaintext: ByteArray, + ): Pair { + val subjectPublicKey = SubjectPublicKeyInfo.getInstance(recipientPublicKey).publicKeyData.bytes + val hpke = + HPKE( + HPKE.mode_base, + HPKE.kem_P256_SHA256, + HPKE.kdf_HKDF_SHA256, + HPKE.aead_AES_GCM256, + ) + val sealed = hpke.seal(hpke.deserializePublicKey(subjectPublicKey), byteArrayOf(), byteArrayOf(), plaintext, null, null, null) + return sealed[1] to sealed[0] + } + + private fun requireValidEthereumScalar(value: ByteArray) { + val scalar = BigInteger(1, value) + require(scalar.signum() > 0 && scalar < secp256k1Order) { + "Ethereum privateKey is outside the valid secp256k1 scalar range" + } + } + + private fun decodeBase58(value: String): ByteArray { + require(value.isNotEmpty()) { "Solana privateKey is required" } + var decoded = BigInteger.ZERO + value.forEach { character -> + val digit = base58Alphabet.indexOf(character) + require(digit >= 0) { "Solana privateKey must be base58 encoded" } + decoded = decoded.multiply(BigInteger.valueOf(58)).add(BigInteger.valueOf(digit.toLong())) + } + val encoded = decoded.toByteArray().let { if (it.size > 1 && it[0] == 0.toByte()) it.copyOfRange(1, it.size) else it } + return ByteArray(value.takeWhile { it == '1' }.length) + + if (decoded == BigInteger.ZERO) byteArrayOf() else encoded + } + + private fun String.trimAsciiWhitespace(): String = trim { it == ' ' || it == '\t' || it == '\n' || it == '\r' || it == '\u000c' } + + private fun String.hexBytes(): ByteArray = chunked(2).map { it.toInt(16).toByte() }.toByteArray() +} + +internal object WalletImportBase64 { + private const val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + + fun encode(value: ByteArray): String = + buildString((value.size + 2) / 3 * 4) { + var offset = 0 + while (offset < value.size) { + val remaining = value.size - offset + val bits = + ((value[offset].toInt() and 0xff) shl 16) or + (if (remaining > 1) (value[offset + 1].toInt() and 0xff) shl 8 else 0) or + (if (remaining > 2) value[offset + 2].toInt() and 0xff else 0) + append(alphabet[bits ushr 18]) + append(alphabet[(bits ushr 12) and 63]) + append(if (remaining > 1) alphabet[(bits ushr 6) and 63] else '=') + append(if (remaining > 2) alphabet[bits and 63] else '=') + offset += 3 + } + } + + fun decodeCanonical( + value: String, + field: String, + ): ByteArray { + require(value.isNotEmpty() && value.length % 4 == 0) { "$field must be canonical base64" } + val padding = value.takeLastWhile { it == '=' }.length + require(padding <= 2 && value.dropLast(padding).none { it == '=' }) { "$field must be canonical base64" } + val output = ArrayList(value.length / 4 * 3) + value.chunked(4).forEach { chunk -> + val digits = chunk.map { if (it == '=') 0 else alphabet.indexOf(it) } + require(digits.all { it >= 0 }) { "$field must be canonical base64" } + val bits = (digits[0] shl 18) or (digits[1] shl 12) or (digits[2] shl 6) or digits[3] + output += (bits ushr 16).toByte() + if (chunk[2] != '=') output += (bits ushr 8).toByte() + if (chunk[3] != '=') output += bits.toByte() + } + val decoded = output.toByteArray() + require(decoded.isNotEmpty() && encode(decoded) == value) { "$field must be canonical base64" } + return decoded + } +} diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt index c064249..e517c72 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt @@ -301,8 +301,9 @@ class PublicErrorContractsTest { MockResponse .Builder() .code(200) - .body("""{"wallet":{"id":"wallet-created","type":"ethereum","address":"0x4444444444444444444444444444444444444444"}}""") - .bodyDelay(300, TimeUnit.MILLISECONDS) + .body( + """{"wallet":{"id":"wallet-created","type":"ethereum","networkFamily":"evm","keyOrigin":"enclave","address":"0x4444444444444444444444444444444444444444"}}""", + ).bodyDelay(300, TimeUnit.MILLISECONDS) .build(), ) val inFlightClient = createOmsClient() diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/network/ServiceClientsTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/network/ServiceClientsTest.kt index b2811b6..83e6651 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/network/ServiceClientsTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/network/ServiceClientsTest.kt @@ -18,7 +18,9 @@ import technology.polygon.omswallet.OMSWalletErrorCode import technology.polygon.omswallet.OMSWalletException import technology.polygon.omswallet.OMSWalletOperation import technology.polygon.omswallet.OMSWalletUpstreamService +import technology.polygon.omswallet.SolanaNetwork import technology.polygon.omswallet.indexer.IndexerClient +import technology.polygon.omswallet.models.SolanaBalance import technology.polygon.omswallet.models.TokenBalancesPageRequest import technology.polygon.omswallet.session.OMSWalletSessionSnapshot @@ -86,7 +88,7 @@ class ServiceClientsTest { assertEquals(null, messageRequest.headers["Authorization"]) assertEquals(null, messageRequest.headers[OMSWalletEnvironment.walletSignatureHeaderName]) assertEquals( - """{"network":"80002","walletId":"wallet-id","message":"hello","signature":"0xmessage"}""", + """{"network":"80002","networkFamily":"evm","walletId":"wallet-id","message":"hello","signature":"0xmessage"}""", requireNotNull(messageRequest.body).utf8(), ) assertEquals(true, messageIsValid) @@ -226,6 +228,44 @@ class ServiceClientsTest { assertEquals(6, balance.contractInfo?.decimals) } + @Test + fun getSolanaBalancesUsesSolanaGatewayAndDecodesAssets() = + runBlocking { + server.enqueue( + MockResponse + .Builder() + .code(200) + .body( + """{"balances":[{"network":"solana:mainnet","accountAddress":"solana-wallet","assetType":"native","name":"Solana","symbol":"SOL","decimals":9,"balance":"1","formattedBalance":"0.000000001","verificationStatus":"unknown","verificationSource":"none"},{"network":"solana:mainnet","accountAddress":"solana-wallet","assetType":"fungible-token","tokenProgram":"spl-token","mintAddress":"usdc-mint","name":"USD Coin","symbol":"USDC","decimals":6,"balance":"10","formattedBalance":"0.00001","verificationStatus":"verified","verificationSource":"jupiter"}],"errors":[{"network":"solana:devnet","reason":"RPC unavailable"}]}""", + ).build(), + ) + val environment = + OMSWalletEnvironment( + walletApiUrl = server.url("/v1/Waas/").toString(), + indexerGatewayUrl = server.url("/v1/IndexerGateway/").toString(), + solanaIndexerGatewayUrl = server.url("/v1/SolanaIndexerGateway/").toString(), + ) + val client = IndexerClient.create("test-publishable-key", environment, OMSWalletHttpClient()) + + val result = + client.getSolanaBalances( + walletAddress = "solana-wallet", + includeMetadata = false, + omitNativeBalances = false, + mintAddresses = listOf("usdc-mint"), + excludedMintAddresses = listOf("spam-mint"), + ) + val request = requireNotNull(server.takeRequest()) + + assertEquals("/v1/SolanaIndexerGateway/GetTokenBalancesDetails", request.target) + assertEquals( + "webrpc@v0.31.2;gen-kotlin@v0.3.2;solana-indexer-gateway@v1", + request.headers["Webrpc"], + ) + assertTrue(result.balances[1] is SolanaBalance.FungibleToken) + assertEquals(SolanaNetwork.Devnet, result.errors.first().network) + } + @Test fun getBalancesDefaultsToMainnetsWhenNetworksAreOmitted() = runBlocking { diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletAccessTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletAccessTest.kt index 8682097..0d1f8da 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletAccessTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletAccessTest.kt @@ -11,6 +11,7 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test +import technology.polygon.omswallet.Network import technology.polygon.omswallet.OMSWalletErrorCode import technology.polygon.omswallet.OMSWalletException import technology.polygon.omswallet.internal.generated.waas.GetIDTokenRequest @@ -18,9 +19,11 @@ import technology.polygon.omswallet.internal.generated.waas.ListAccessRequest import technology.polygon.omswallet.internal.generated.waas.Page import technology.polygon.omswallet.internal.generated.waas.RevokeAccessRequest import technology.polygon.omswallet.internal.generated.waas.WaasApi +import technology.polygon.omswallet.models.SmartSessionGrant import technology.polygon.omswallet.network.OMSWalletEnvironment import technology.polygon.omswallet.network.OMSWalletHttpClient import technology.polygon.omswallet.session.OMSWalletSessionSnapshot +import java.math.BigInteger class WalletAccessTest { private lateinit var server: MockWebServer @@ -49,6 +52,7 @@ class WalletAccessTest { "credentials": [ { "credentialId": "credential-1", + "type": "direct", "expiresAt": "2099-01-01T00:00:00Z", "isCaller": true } @@ -68,6 +72,7 @@ class WalletAccessTest { "credentials": [ { "credentialId": "credential-2", + "type": "direct", "expiresAt": "2026-01-02T00:00:00Z", "isCaller": false } @@ -111,13 +116,13 @@ class WalletAccessTest { assertTrue(client.restorePersistedSession()) val credentials = client.listAccess(pageSize = 2u) - client.revokeAccess(targetCredentialId = "credential-2") + client.revokeAccess(credentialId = "credential-2") val firstListRequest = requireNotNull(server.takeRequest()) val secondListRequest = requireNotNull(server.takeRequest()) val revokeRequest = requireNotNull(server.takeRequest()) - assertEquals(listOf("credential-1", "credential-2"), credentials.map { it.credentialId }) - assertEquals(true, credentials.first().isCaller) + assertEquals(listOf("credential-1", "credential-2"), credentials.map { it.credential.credentialId }) + assertEquals(true, credentials.first().credential.isCaller) assertEquals("/v1/Waas/ListAccess", firstListRequest.target) assertEquals( WaasApi.ListAccess.encodeRequest( @@ -165,6 +170,7 @@ class WalletAccessTest { "credentials": [ { "credentialId": "credential-1", + "type": "direct", "expiresAt": "2099-01-01T00:00:00Z", "isCaller": true } @@ -184,6 +190,7 @@ class WalletAccessTest { "credentials": [ { "credentialId": "credential-2", + "type": "direct", "expiresAt": "2026-01-02T00:00:00Z", "isCaller": false } @@ -224,9 +231,9 @@ class WalletAccessTest { val secondListRequest = requireNotNull(server.takeRequest()) assertEquals(2, pages.size) - assertEquals(listOf("credential-1"), pages[0].credentials.map { it.credentialId }) + assertEquals(listOf("credential-1"), pages[0].grants.map { it.credential.credentialId }) assertEquals("next", pages[0].page?.cursor) - assertEquals(listOf("credential-2"), pages[1].credentials.map { it.credentialId }) + assertEquals(listOf("credential-2"), pages[1].grants.map { it.credential.credentialId }) assertEquals(null, pages[1].page?.cursor) assertEquals( WaasApi.ListAccess.encodeRequest( @@ -248,6 +255,74 @@ class WalletAccessTest { ) } + @Test + fun ownerSmartSessionFlowMapsRequestsAndResponses() = + runBlocking { + server.enqueue( + MockResponse + .Builder() + .code(200) + .body( + """{"metadata":{"appUrl":"https://app.example","appName":"Example","appLogoUrl":"https://app.example/logo.png","custom":{"environment":"test"}}}""", + ).build(), + ) + server.enqueue( + MockResponse + .Builder() + .code(200) + .body( + """{"sessionId":"session-1","expiry":"2099-01-01T00:00:00Z"}""", + ).build(), + ) + server.enqueue( + MockResponse + .Builder() + .code(200) + .body( + """{"session":{"sessionId":"session-1","walletId":"wallet-main","signerAddress":"0x3333333333333333333333333333333333333333","grants":{"entries":[{"kind":"nativeTransfer","nativeTransfer":{"to":"0x2222222222222222222222222222222222222222","limit":"100"}}]},"chainId":"137","expiresAt":"2099-01-01T00:00:00Z"}}""", + ).build(), + ) + server.enqueue( + MockResponse + .Builder() + .code(200) + .body( + """{"entries":[{"grant":{"kind":"nativeTransfer","nativeTransfer":{"to":"0x2222222222222222222222222222222222222222","limit":"100"}},"used":"25"}]}""", + ).build(), + ) + val client = restoredWalletClient("1710000114") + val grant = + SmartSessionGrant.NativeTransfer( + to = "0x2222222222222222222222222222222222222222", + limit = BigInteger("100"), + ) + + val metadata = client.inspectRemoteCredential("remote-credential") + val authorization = + client.authorizeRemoteAccess( + credentialId = "remote-credential", + network = Network.POLYGON, + grants = listOf(grant), + expiresAt = "2099-01-01T00:00:00Z", + ) + val session = client.getRemoteAccessSession(authorization.sessionId) + val usage = client.getRemoteAccessSessionUsage(authorization.sessionId, Network.POLYGON) + val inspect = requireNotNull(server.takeRequest()) + val authorize = requireNotNull(server.takeRequest()) + val getSession = requireNotNull(server.takeRequest()) + val getUsage = requireNotNull(server.takeRequest()) + + assertEquals("Example", metadata.appName) + assertEquals("/v1/WaasPublic/InspectCredential", inspect.target) + assertEquals("/v1/Waas/AuthorizeRemoteAccess", authorize.target) + assertTrue(requireNotNull(authorize.body).utf8().contains("\"chainId\":\"137\"")) + assertEquals("/v1/Waas/GetSession", getSession.target) + assertEquals(137, session.chainId) + assertEquals(listOf(grant), session.grants) + assertEquals("/v1/Waas/GetSessionUsage", getUsage.target) + assertEquals(BigInteger("25"), usage.single().used) + } + @Test fun getIdTokenUsesGeneratedWaasRequest() = runBlocking { @@ -332,11 +407,36 @@ class WalletAccessTest { val error = runCatching { - client.revokeAccess(targetCredentialId = "credential-2") + client.revokeAccess(credentialId = "credential-2") }.exceptionOrNull() assertTrue(error is OMSWalletException) assertEquals(OMSWalletErrorCode.SessionMissing, (error as OMSWalletException).code) assertEquals(0, server.requestCount) } + + private fun restoredWalletClient(nonce: String): WalletClient = + WalletClient + .create( + publishableKey = "test-publishable-key", + projectId = "test-project-id", + environment = + OMSWalletEnvironment( + walletApiUrl = server.url("/v1/Waas/").toString(), + indexerGatewayUrl = server.url("/v1/IndexerGateway/").toString(), + ), + transport = OMSWalletHttpClient(), + sessionStore = + InMemorySessionStore( + snapshot = + OMSWalletSessionSnapshot( + walletId = "wallet-main", + walletAddress = "0x1111111111111111111111111111111111111111", + signerAddress = TEST_CREDENTIAL_ID, + signerKeyType = WalletSigningAlgorithm.ECDSA_P256_SHA256, + auth = emailSessionAuth(), + ), + ), + credentialSigner = TrackingCredentialSigner(nonceValue = nonce), + ).also { assertTrue(it.restorePersistedSession()) } } diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletClientTestFixtures.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletClientTestFixtures.kt index 9c870fe..6fa2dc5 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletClientTestFixtures.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletClientTestFixtures.kt @@ -4,9 +4,12 @@ import kotlinx.serialization.encodeToString import org.junit.Assert.assertTrue import technology.polygon.omswallet.internal.generated.waas.CompleteAuthResponse import technology.polygon.omswallet.internal.generated.waas.CredentialInfo +import technology.polygon.omswallet.internal.generated.waas.CredentialType import technology.polygon.omswallet.internal.generated.waas.Identity import technology.polygon.omswallet.internal.generated.waas.IdentityType +import technology.polygon.omswallet.internal.generated.waas.KeyOrigin import technology.polygon.omswallet.internal.generated.waas.ListWalletsResponse +import technology.polygon.omswallet.internal.generated.waas.NetworkFamily import technology.polygon.omswallet.internal.generated.waas.Page import technology.polygon.omswallet.internal.generated.waas.Wallet import technology.polygon.omswallet.internal.generated.waas.WalletType @@ -39,6 +42,13 @@ internal fun walletFixture( Wallet( id = walletId, type = type, + networkFamily = + when (type) { + WalletType.Ethereum -> NetworkFamily.EVM + WalletType.Solana -> NetworkFamily.Solana + WalletType.UNKNOWN_DEFAULT -> NetworkFamily.UNKNOWN_DEFAULT + }, + keyOrigin = KeyOrigin.Enclave, address = address, reference = reference, ) @@ -73,6 +83,7 @@ internal fun completeAuthResponseBody( internal fun credentialFixture(): CredentialInfo = CredentialInfo( credentialId = "credential-123", + type = CredentialType.Direct, expiresAt = "2099-01-01T00:00:00Z", isCaller = true, ) diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletEmailAuthTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletEmailAuthTest.kt index b511eb3..605502d 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletEmailAuthTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletEmailAuthTest.kt @@ -642,6 +642,8 @@ class WalletEmailAuthTest { Wallet( id = "wallet-def", type = WalletType.Ethereum, + networkFamily = technology.polygon.omswallet.internal.generated.waas.NetworkFamily.EVM, + keyOrigin = technology.polygon.omswallet.internal.generated.waas.KeyOrigin.Enclave, address = "0xdef", reference = "picked", ), @@ -785,7 +787,7 @@ class WalletEmailAuthTest { walletFixture( walletId = "wallet-other", address = "0xother", - type = WalletType.UNKNOWN_DEFAULT, + type = WalletType.Solana, ), ), page = Page(cursor = "cursor-2"), @@ -1040,7 +1042,7 @@ class WalletEmailAuthTest { walletId = "wallet-other", address = "0xother", reference = "other", - type = WalletType.UNKNOWN_DEFAULT, + type = WalletType.Solana, ), ), ), @@ -1089,7 +1091,7 @@ class WalletEmailAuthTest { assertEquals( WaasApi.CreateWallet.encodeRequest( CreateWalletRequest( - type = WalletType.Ethereum, + networkFamily = technology.polygon.omswallet.internal.generated.waas.NetworkFamily.EVM, reference = "fresh", ), ), diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletImportCryptoTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletImportCryptoTest.kt new file mode 100644 index 0000000..0defb42 --- /dev/null +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletImportCryptoTest.kt @@ -0,0 +1,57 @@ +package technology.polygon.omswallet.wallet + +import org.bouncycastle.crypto.hpke.HPKE +import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertThrows +import org.junit.Test +import technology.polygon.omswallet.WalletImportConfiguration +import technology.polygon.omswallet.models.WalletImportPrivateKey + +class WalletImportCryptoTest { + @Test + fun configurationRejectsMalformedAndAllZeroPcr0s() { + listOf( + emptyList(), + listOf("0".repeat(95)), + listOf("0".repeat(96)), + listOf("z".repeat(96)), + ).forEach { values -> + assertThrows(IllegalArgumentException::class.java) { WalletImportConfiguration(values) } + } + WalletImportConfiguration(listOf("0x" + "a".repeat(96))) + } + + @Test + fun privateKeyValidationCoversScalarAndLengthBoundaries() { + val one = ByteArray(32).also { it[31] = 1 } + assertArrayEquals(one, WalletImportCrypto.plaintext(WalletImportPrivateKey.EthereumBytes(one))) + assertThrows(IllegalArgumentException::class.java) { + WalletImportCrypto.plaintext(WalletImportPrivateKey.EthereumBytes(ByteArray(32))) + } + assertThrows(IllegalArgumentException::class.java) { + WalletImportCrypto.plaintext( + WalletImportPrivateKey.Ethereum("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"), + ) + } + assertThrows(IllegalArgumentException::class.java) { + WalletImportCrypto.plaintext(WalletImportPrivateKey.SolanaBytes(ByteArray(31))) + } + assertThrows(IllegalArgumentException::class.java) { + WalletImportCrypto.validateReference("é".repeat(65)) + } + } + + @Test + fun p256HpkeCiphertextOpensWithStandardBouncyCastleReceiver() { + val hpke = HPKE(HPKE.mode_base, HPKE.kem_P256_SHA256, HPKE.kdf_HKDF_SHA256, HPKE.aead_AES_GCM256) + val recipient = hpke.generatePrivateKey() + val spki = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(recipient.public).encoded + val plaintext = ("0x" + "11".repeat(32)).toByteArray() + + val (encapsulation, ciphertext) = WalletImportCrypto.sealP256Aes256Gcm(spki, plaintext) + val opened = hpke.open(encapsulation, recipient, byteArrayOf(), byteArrayOf(), ciphertext, null, null, null) + + assertArrayEquals(plaintext, opened) + } +} diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletTransactionTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletTransactionTest.kt index 0380931..8822cb4 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletTransactionTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletTransactionTest.kt @@ -15,6 +15,7 @@ import technology.polygon.omswallet.Network import technology.polygon.omswallet.OMSWalletErrorCode import technology.polygon.omswallet.OMSWalletException import technology.polygon.omswallet.OMSWalletOperation +import technology.polygon.omswallet.SolanaNetwork import technology.polygon.omswallet.internal.generated.waas.ExecuteRequest import technology.polygon.omswallet.internal.generated.waas.PrepareEthereumContractCallRequest import technology.polygon.omswallet.internal.generated.waas.TransactionStatusRequest @@ -290,7 +291,7 @@ class WalletTransactionTest { WaasApi.Execute.encodeRequest( technology.polygon.omswallet.internal.generated.waas.ExecuteRequest( txnId = "txn-1", - feeOption = WaasFeeOptionSelection(token = "usdc"), + feeOption = WaasFeeOptionSelection(token = "usdc", index = 1u), ), ), requireNotNull(executeRequest.body).utf8(), @@ -357,7 +358,7 @@ class WalletTransactionTest { WaasApi.Execute.encodeRequest( ExecuteRequest( txnId = "txn-token-id", - feeOption = WaasFeeOptionSelection(token = "usdc"), + feeOption = WaasFeeOptionSelection(token = "usdc", index = 0u), ), ), requireNotNull(executeRequest.body).utf8(), @@ -565,7 +566,7 @@ class WalletTransactionTest { WaasApi.Execute.encodeRequest( ExecuteRequest( txnId = "txn-first-available", - feeOption = WaasFeeOptionSelection(token = "usdc"), + feeOption = WaasFeeOptionSelection(token = "usdc", index = 1u), ), ), requireNotNull(executeRequest.body).utf8(), @@ -1165,6 +1166,48 @@ class WalletTransactionTest { assertEquals(null, unknown.txnHash) } + @Test + fun solanaOperationsUseSolanaRequestShapes() = + runBlocking { + enqueueJson("""{"signature":"solana-signature"}""") + enqueueJson("""{"isValid":true}""") + enqueueJson( + """{"txnId":"solana-txn","status":"quoted","feeOptions":[],"sponsored":true,"expiresAt":"2099-01-01T00:00:00Z"}""", + ) + enqueueJson("""{"status":"executed"}""") + val client = + restoredWalletClient( + nonceValue = "1710000120", + walletAddress = "3gFktQX6vki5M2DzN8Y1ESPUJ4fJ8o6hVQWf8vYvPypD", + ) + + val signature = client.signSolanaMessage("hello") + val valid = client.isValidSolanaMessageSignature("hello", signature) + val transaction = + client.sendSolanaTransfer( + network = SolanaNetwork.Devnet, + asset = "SOL", + to = "recipient", + amount = BigInteger("1000000"), + waitForStatus = false, + ) + val sign = requireNotNull(server.takeRequest()) + val verify = requireNotNull(server.takeRequest()) + val prepare = requireNotNull(server.takeRequest()) + val execute = requireNotNull(server.takeRequest()) + + assertEquals("/v1/Waas/SignMessage", sign.target) + assertTrue(requireNotNull(sign.body).utf8().contains("\"network\":\"\"")) + assertEquals("/v1/WaasPublic/IsValidMessageSignature", verify.target) + assertTrue(requireNotNull(verify.body).utf8().contains("\"networkFamily\":\"solana\"")) + assertTrue(valid) + assertEquals("/v1/Waas/PrepareSolanaTransfer", prepare.target) + assertTrue(requireNotNull(prepare.body).utf8().contains("\"network\":\"solana:devnet\"")) + assertEquals("/v1/Waas/Execute", execute.target) + assertEquals("solana-txn", transaction.txnId) + assertEquals(TransactionStatusResolution.NotRequested, transaction.statusResolution) + } + private fun enqueueJson(body: String) { server.enqueue( MockResponse @@ -1177,6 +1220,7 @@ class WalletTransactionTest { private fun restoredWalletClient( nonceValue: String, + walletAddress: String = "0xwallet", environment: OMSWalletEnvironment = OMSWalletEnvironment( walletApiUrl = server.url("/v1/Waas/").toString(), @@ -1194,7 +1238,7 @@ class WalletTransactionTest { snapshot = OMSWalletSessionSnapshot( walletId = "wallet-main", - walletAddress = "0xwallet", + walletAddress = walletAddress, signerAddress = TEST_CREDENTIAL_ID, signerKeyType = WalletSigningAlgorithm.ECDSA_P256_SHA256, auth = emailSessionAuth(), From 7455139d0598abcaf0d0ece59b91e505b3ac1724 Mon Sep 17 00:00:00 2001 From: tolgahan-arikan Date: Fri, 4 Sep 2026 18:18:27 +0300 Subject: [PATCH 02/10] test: cover wallet import security boundaries Exercise canonical Base64 validation and fail-closed attestation checks, document the intentional Solana key ambiguity guard, and correct the access-inspection error contract. --- docs/error-contracts.md | 2 +- .../wallet/WalletImportCryptoTest.kt | 185 ++++++++++++++++++ 2 files changed, 186 insertions(+), 1 deletion(-) diff --git a/docs/error-contracts.md b/docs/error-contracts.md index a624023..595f88c 100644 --- a/docs/error-contracts.md +++ b/docs/error-contracts.md @@ -47,7 +47,7 @@ whether `upstreamError` should be present, and which tests own the contract. | OIDC redirect/id-token auth methods | Local OIDC config, callback, or state mismatch | `OMSWalletSessionException` or `OMSWalletValidationException` | Fix redirect config/state or restart OIDC flow | Absent | `PublicErrorContractsTest` | | `client.wallet.startOidcRedirectAuth` | Local OIDC redirect-state persistence failure | `OMSWalletStorageException`, `OMS_STORAGE_ERROR` | Retry starting OIDC auth after the local storage issue is resolved | Absent | `PublicErrorContractsTest` | | `client.wallet.signOut` | Persistent session, redirect-state, or signer cleanup failure | `OMSWalletStorageException`, `OMS_STORAGE_ERROR`; in-memory session is already cleared | Keep the user signed out locally; report or retry persistent cleanup as appropriate | Absent | `WalletSessionTest` | -| Protected wallet methods: `getIdToken`, signing and transaction methods, wallet import, `getTransactionStatus`, access inspection/authorization/usage/listing/revocation | Missing, expired, or stale local session | `OMSWalletSessionException` | Authenticate again or recover local session; no remote request was made | Absent | `PublicErrorContractsTest` | +| Protected wallet methods: `getIdToken`, signing and transaction methods, wallet import, `getTransactionStatus`, access authorization/usage/listing/revocation | Missing, expired, or stale local session | `OMSWalletSessionException` | Authenticate again or recover local session; no remote request was made | Absent | `PublicErrorContractsTest` | | Wallet auth, signing, transactions, import, and owner access methods | SDK-local validation or fee-selection failure | `OMSWalletValidationException` | Correct parameters or local fee selection; do not retry as an upstream outage | Absent | `PublicErrorContractsTest`, `WalletImportCryptoTest`, `WalletAccessTest` | | `client.wallet.getWalletImportRecipientKey`, `importWallet`, `importEncryptedWallet` | Recipient-key attestation is missing, stale, malformed, untrusted, or does not match the request/response | `OMSWalletAttestationException`, `OMS_ATTESTATION_VERIFICATION_FAILED` | Do not encrypt or submit key material; retry only after confirming the configured PCR0 allowlist and WaaS environment | Absent | `WalletImportCryptoTest` | | `client.wallet.isValidMessageSignature`, `isValidTypedDataSignature` | WaaS validation backend failure | `OMSWalletRequestException` or `OMSWalletResponseException` with validation operation | Retry based on SDK code/status; log upstream detail | Present | `PublicErrorContractsTest` | diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletImportCryptoTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletImportCryptoTest.kt index 0defb42..05a0960 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletImportCryptoTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletImportCryptoTest.kt @@ -1,12 +1,18 @@ package technology.polygon.omswallet.wallet +import com.upokecenter.cbor.CBORObject import org.bouncycastle.crypto.hpke.HPKE import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue import org.junit.Test +import technology.polygon.omswallet.OMSWalletAttestationException +import technology.polygon.omswallet.OMSWalletErrorCode import technology.polygon.omswallet.WalletImportConfiguration import technology.polygon.omswallet.models.WalletImportPrivateKey +import java.security.MessageDigest class WalletImportCryptoTest { @Test @@ -37,11 +43,110 @@ class WalletImportCryptoTest { assertThrows(IllegalArgumentException::class.java) { WalletImportCrypto.plaintext(WalletImportPrivateKey.SolanaBytes(ByteArray(31))) } + assertThrows(IllegalArgumentException::class.java) { + WalletImportCrypto.plaintext(WalletImportPrivateKey.Solana("1".repeat(32))) + } assertThrows(IllegalArgumentException::class.java) { WalletImportCrypto.validateReference("é".repeat(65)) } } + @Test + fun canonicalBase64CoversPaddingAndRejectsNonCanonicalInputs() { + listOf( + byteArrayOf(0), + byteArrayOf(0, 1), + byteArrayOf(0, 1, 2), + byteArrayOf(0, 1, 2, 3), + ).forEach { value -> + assertArrayEquals(value, WalletImportBase64.decodeCanonical(WalletImportBase64.encode(value), "value")) + } + + listOf("", "AQ", "AB==", "A===", "AA=A").forEach { value -> + assertThrows(IllegalArgumentException::class.java) { + WalletImportBase64.decodeCanonical(value, "value") + } + } + } + + @Test + fun attestationVerifierRejectsUntrustedOrMismatchedDocuments() { + val now = 1_800_000_000_000L + val trustedPcr0 = ByteArray(48) { 0xaa.toByte() } + val trustedPcr0s = setOf(trustedPcr0.toHex()) + val requestBody = "{}" + val responseBody = """{"keyId":"key-id"}""" + val nonce = "test-nonce" + + expectAttestationFailure("invalid COSE_Sign1 structure") { + verifyAttestation( + encodedDocument = WalletImportBase64.encode(byteArrayOf(0)), + requestBody = requestBody, + responseBody = responseBody, + nonce = nonce, + trustedPcr0s = trustedPcr0s, + now = now, + ) + } + expectAttestationFailure("freshness window") { + verifySyntheticAttestation( + timestamp = now - 6 * 60 * 1_000, + pcr0 = trustedPcr0, + nonce = nonce, + requestBody = requestBody, + responseBody = responseBody, + trustedPcr0s = trustedPcr0s, + now = now, + ) + } + expectAttestationFailure("PCR0 is not trusted") { + verifySyntheticAttestation( + timestamp = now, + pcr0 = ByteArray(48) { 0xbb.toByte() }, + nonce = nonce, + requestBody = requestBody, + responseBody = responseBody, + trustedPcr0s = trustedPcr0s, + now = now, + ) + } + expectAttestationFailure("nonce does not match") { + verifySyntheticAttestation( + timestamp = now, + pcr0 = trustedPcr0, + nonce = nonce, + requestBody = requestBody, + responseBody = responseBody, + trustedPcr0s = trustedPcr0s, + now = now, + verificationNonce = "different-nonce", + ) + } + expectAttestationFailure("not bound to the request and response") { + verifySyntheticAttestation( + timestamp = now, + pcr0 = trustedPcr0, + nonce = nonce, + requestBody = requestBody, + responseBody = responseBody, + trustedPcr0s = trustedPcr0s, + now = now, + verificationResponseBody = "{}", + ) + } + expectAttestationFailure("does not use the AWS Nitro root") { + verifySyntheticAttestation( + timestamp = now, + pcr0 = trustedPcr0, + nonce = nonce, + requestBody = requestBody, + responseBody = responseBody, + trustedPcr0s = trustedPcr0s, + now = now, + ) + } + } + @Test fun p256HpkeCiphertextOpensWithStandardBouncyCastleReceiver() { val hpke = HPKE(HPKE.mode_base, HPKE.kem_P256_SHA256, HPKE.kdf_HKDF_SHA256, HPKE.aead_AES_GCM256) @@ -54,4 +159,84 @@ class WalletImportCryptoTest { assertArrayEquals(plaintext, opened) } + + private fun verifySyntheticAttestation( + timestamp: Long, + pcr0: ByteArray, + nonce: String, + requestBody: String, + responseBody: String, + trustedPcr0s: Set, + now: Long, + verificationNonce: String = nonce, + verificationResponseBody: String = responseBody, + ) { + val method = "POST" + val path = "/v1/Waas/GetRecipientKey" + val preimage = "$method $path\n$requestBody\n$responseBody" + val hash = WalletImportBase64.encode(MessageDigest.getInstance("SHA-256").digest(preimage.toByteArray())) + val protectedHeader = CBORObject.NewMap().apply { Add(1, -35) }.EncodeToBytes() + val pcrs = CBORObject.NewMap().apply { Add(0, pcr0) } + val payload = + CBORObject + .NewMap() + .apply { + Add("digest", "SHA384") + Add("timestamp", timestamp) + Add("pcrs", pcrs) + Add("certificate", byteArrayOf(1)) + Add("cabundle", CBORObject.NewArray().apply { Add(byteArrayOf(2)) }) + Add("user_data", "Sequence/1:$hash".toByteArray()) + Add("nonce", nonce.toByteArray()) + }.EncodeToBytes() + val document = + CBORObject + .NewArray() + .apply { + Add(protectedHeader) + Add(CBORObject.NewMap()) + Add(payload) + Add(ByteArray(96)) + }.WithTag(18) + + verifyAttestation( + encodedDocument = WalletImportBase64.encode(document.EncodeToBytes()), + requestBody = requestBody, + responseBody = verificationResponseBody, + nonce = verificationNonce, + trustedPcr0s = trustedPcr0s, + now = now, + ) + } + + private fun verifyAttestation( + encodedDocument: String, + requestBody: String, + responseBody: String, + nonce: String, + trustedPcr0s: Set, + now: Long, + ) { + AttestationVerifier.verify( + encodedDocument = encodedDocument, + method = "POST", + path = "/v1/Waas/GetRecipientKey", + requestBody = requestBody, + responseBody = responseBody, + nonce = nonce, + trustedPcr0s = trustedPcr0s, + nowMillis = now, + ) + } + + private fun expectAttestationFailure( + expectedMessage: String, + operation: () -> Unit, + ) { + val exception = assertThrows(OMSWalletAttestationException::class.java, operation) + assertEquals(OMSWalletErrorCode.AttestationVerificationFailed, exception.code) + assertTrue(requireNotNull(exception.message).contains(expectedMessage)) + } + + private fun ByteArray.toHex(): String = joinToString("") { "%02x".format(it) } } From 2a87f9d35b4c908add0377eb03abfc69336a3141 Mon Sep 17 00:00:00 2001 From: tolgahan-arikan Date: Mon, 7 Sep 2026 13:50:51 +0300 Subject: [PATCH 03/10] fix: preserve wallet import attestation errors --- .../polygon/omswallet/OMSWalletError.kt | 22 ++++++++--- .../omswallet/PublicErrorContractsTest.kt | 39 ++++++++++++++++++- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWalletError.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWalletError.kt index bae0472..cb394f3 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWalletError.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWalletError.kt @@ -273,6 +273,10 @@ internal suspend fun runOMSWalletOperation( } catch (throwable: WebRpcError) { throw throwable.toOMSWalletException(operation) } catch (throwable: WebRpcTransportException) { + val attestationFailure = throwable.attestationFailure(operation) + if (attestationFailure != null) { + throw attestationFailure + } throw OMSWalletRequestException( operation = operation, upstreamError = throwable.toWaasUpstreamError(), @@ -366,12 +370,13 @@ internal fun Throwable.toOMSWalletException(operation: OMSWalletOperation): OMSW } is WebRpcTransportException -> { - OMSWalletRequestException( - operation = operation, - upstreamError = toWaasUpstreamError(), - message = message ?: "WebRPC transport failed", - cause = this, - ) + attestationFailure(operation) + ?: OMSWalletRequestException( + operation = operation, + upstreamError = toWaasUpstreamError(), + message = message ?: "WebRPC transport failed", + cause = this, + ) } is IllegalArgumentException -> { @@ -479,6 +484,11 @@ private fun OMSWalletException.withOperation(operation: OMSWalletOperation): OMS } } +private fun WebRpcTransportException.attestationFailure(operation: OMSWalletOperation): OMSWalletException? = + (cause as? OMSWalletAttestationException)?.let { failure -> + if (failure.operation == operation) failure else failure.withOperation(operation) + } + private fun WebRpcError.normalizedStatus(): Int? { if (error == "WebrpcRequestFailed" && code == ErrorKind.WEBRPC_REQUEST_FAILED.code && status == 400) { return null diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt index e517c72..f9e7762 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt @@ -20,6 +20,7 @@ import org.junit.Test import technology.polygon.omswallet.indexer.IndexerClient import technology.polygon.omswallet.models.AbiArg import technology.polygon.omswallet.models.SendTransactionRequest +import technology.polygon.omswallet.models.WalletImportCipherSuite import technology.polygon.omswallet.network.OMSWalletEnvironment import technology.polygon.omswallet.network.OMSWalletHttpClient import technology.polygon.omswallet.session.OMSWalletSessionSnapshot @@ -85,6 +86,35 @@ class PublicErrorContractsTest { ) } + @Test + fun preservesWalletImportAttestationFailuresAcrossWebRpc() = + runBlocking { + server.enqueue( + MockResponse + .Builder() + .code(200) + .body("{}") + .build(), + ) + val client = + createOmsClientWithSession( + walletImport = WalletImportConfiguration(listOf("a".repeat(96))), + ) + + assertEquals( + error( + name = "OMSWalletAttestationException", + code = "OMS_ATTESTATION_VERIFICATION_FAILED", + operation = "wallet.getWalletImportRecipientKey", + message = "WaaS response is missing its attestation document", + retryable = false, + ), + publicError { + client.wallet.getWalletImportRecipientKey(WalletImportCipherSuite.P256Sha256Aes256Gcm) + }, + ) + } + @Test fun snapshotsWaasDomainErrorsWithUpstreamDetails() = runBlocking { @@ -1130,6 +1160,7 @@ class PublicErrorContractsTest { okHttpClient: OkHttpClient = OkHttpClient(), oidcRedirectAuthStore: OidcRedirectAuthStore? = InMemoryOidcRedirectAuthStore(), credentialSigner: CredentialSigner = TrackingCredentialSigner(), + walletImport: WalletImportConfiguration? = null, ): OMSWallet = OMSWallet.createForTesting( publishableKey = "test-publishable-key", @@ -1139,10 +1170,14 @@ class PublicErrorContractsTest { sessionStore = InMemorySessionStore(), oidcRedirectAuthStore = oidcRedirectAuthStore, credentialSigner = credentialSigner, + walletImport = walletImport, ) - private fun createOmsClientWithSession(okHttpClient: OkHttpClient = OkHttpClient()): OMSWallet = - createOmsClient(okHttpClient = okHttpClient).also { client -> + private fun createOmsClientWithSession( + okHttpClient: OkHttpClient = OkHttpClient(), + walletImport: WalletImportConfiguration? = null, + ): OMSWallet = + createOmsClient(okHttpClient = okHttpClient, walletImport = walletImport).also { client -> client.wallet.restoreSession(activeSessionSnapshot()) } From 7f2255ec46b03f611b256138808addeb5ff6ea7c Mon Sep 17 00:00:00 2001 From: tolgahan-arikan Date: Mon, 7 Sep 2026 14:17:45 +0300 Subject: [PATCH 04/10] feat: acknowledge sponsored transactions --- README.md | 13 +++- docs/api.md | 3 + .../omswallet/models/OMSWalletModels.kt | 4 ++ .../polygon/omswallet/wallet/WalletClient.kt | 1 + .../omswallet/wallet/WalletTransactionTest.kt | 66 ++++++++++++++++++- 5 files changed, 82 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 59cb6f8..d385cfc 100644 --- a/README.md +++ b/README.md @@ -588,7 +588,12 @@ val txResult = omsWallet.wallet.sendTransaction( mode = TransactionMode.Native, ), ) { feeOptions -> - feeOptions.first().selection + if (feeOptions.isEmpty()) { + // Present the sponsored transaction for confirmation here. + null + } else { + feeOptions.first().selection + } } ``` @@ -597,8 +602,10 @@ wallet's raw indexer balance for that fee token when available. `available` is formatted with the token decimals, while `availableRaw` keeps the raw integer value. `decimals` is exposed as `Int?`. `selection` preserves the API-provided `tokenID` when present and falls back to the token symbol. Sponsored -transactions skip fee selection; unsponsored transactions fail before execute -when no fee option can be selected. +transactions invoke the selector with an empty list; return `null` after acknowledging +the free fee, or throw to stop execution. `FeeOptionSelector.firstAvailable` returns +`null` for that empty list and continues execution as before. Unsponsored transactions +fail before execute when no fee option can be selected. To refresh a transaction later: diff --git a/docs/api.md b/docs/api.md index 26bed1d..469ad16 100644 --- a/docs/api.md +++ b/docs/api.md @@ -927,6 +927,9 @@ data class TransactionStatusResponse( ### `FeeOptionSelector` +Selects a fee option before execution. Sponsored transactions pass an empty list; +returning null acknowledges the free fee, while throwing stops execution. + ```kotlin fun interface FeeOptionSelector { suspend fun select(feeOptions: List): FeeOptionSelection? diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/OMSWalletModels.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/OMSWalletModels.kt index 1c724b8..c684dc5 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/OMSWalletModels.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/models/OMSWalletModels.kt @@ -169,6 +169,10 @@ data class TransactionStatusResponse( val txnHash: String? = null, ) +/** + * Selects a fee option before execution. Sponsored transactions pass an empty list; + * returning null acknowledges the free fee, while throwing stops execution. + */ fun interface FeeOptionSelector { suspend fun select(feeOptions: List): FeeOptionSelection? diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt index 6ccab54..9c2ee56 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt @@ -2385,6 +2385,7 @@ class WalletClient private constructor( val feeOption = when { prepared.sponsored -> { + selectFeeOption?.select(emptyList()) null } diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletTransactionTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletTransactionTest.kt index 8822cb4..648c924 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletTransactionTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletTransactionTest.kt @@ -1,5 +1,6 @@ package technology.polygon.omswallet.wallet +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.JsonPrimitive import mockwebserver3.MockResponse @@ -367,7 +368,7 @@ class WalletTransactionTest { } @Test - fun sendTransactionSponsoredSkipsCustomFeeSelector() = + fun sendTransactionSponsoredInvokesCustomFeeSelectorWithEmptyOptions() = runBlocking { enqueueJson( prepareResponse( @@ -410,7 +411,7 @@ class WalletTransactionTest { assertEquals("txn-sponsored", result.txnId) assertEquals(TransactionStatus.Executed, result.status) - assertEquals(false, selectorCalled) + assertEquals(true, selectorCalled) assertEquals( WaasApi.Execute.encodeRequest( ExecuteRequest(txnId = "txn-sponsored"), @@ -420,6 +421,67 @@ class WalletTransactionTest { assertEquals(2, server.requestCount) } + @Test + fun sendTransactionSponsoredContinuesWithFirstAvailable() = + runBlocking { + enqueueJson( + prepareResponse( + txnId = "txn-sponsored-first-available", + feeOptions = "[]", + sponsored = true, + ), + ) + enqueueJson("""{"status":"executed"}""") + val client = restoredWalletClient(nonceValue = "1710000117") + + val result = + client.sendTransaction( + network = Network.AMOY, + request = SendTransactionRequest(to = "0xabc", value = BigInteger.ZERO), + waitForStatus = false, + selectFeeOption = FeeOptionSelector.firstAvailable, + ) + + requireNotNull(server.takeRequest()) + val executeRequest = requireNotNull(server.takeRequest()) + assertEquals("txn-sponsored-first-available", result.txnId) + assertEquals( + WaasApi.Execute.encodeRequest( + ExecuteRequest(txnId = "txn-sponsored-first-available"), + ), + requireNotNull(executeRequest.body).utf8(), + ) + } + + @Test + fun sendTransactionSponsoredDoesNotExecuteWhenAcknowledgementThrows() = + runBlocking { + enqueueJson( + prepareResponse( + txnId = "txn-sponsored-cancelled", + feeOptions = "[]", + sponsored = true, + ), + ) + val client = restoredWalletClient(nonceValue = "1710000118") + + val error = + runCatching { + client.sendTransaction( + network = Network.AMOY, + request = SendTransactionRequest(to = "0xabc", value = BigInteger.ZERO), + selectFeeOption = + FeeOptionSelector { feeOptions -> + assertTrue(feeOptions.isEmpty()) + throw CancellationException("Transaction cancelled") + }, + ) + }.exceptionOrNull() + + assertTrue(error is CancellationException) + assertEquals(1, server.requestCount) + } + @Test fun sendTransactionUnsponsoredWithoutFeeOptionsFailsBeforeExecute() = runBlocking { From bf6b98bba363a3b432a83b66d6b6ae72e88e204a Mon Sep 17 00:00:00 2001 From: tolgahan-arikan Date: Mon, 7 Sep 2026 17:09:17 +0300 Subject: [PATCH 05/10] fix(wallet): select available Solana fee options --- README.md | 10 +- oms-wallet-kotlin-sdk/api/public-api.txt | 3 +- .../polygon/omswallet/wallet/WalletClient.kt | 71 ++++++++++- .../omswallet/wallet/WalletTransactionTest.kt | 114 ++++++++++++++++++ 4 files changed, 192 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d385cfc..182bbda 100644 --- a/README.md +++ b/README.md @@ -597,10 +597,12 @@ val txResult = omsWallet.wallet.sendTransaction( } ``` -The selector receives `FeeOptionWithBalance` values. `balance` is the selected -wallet's raw indexer balance for that fee token when available. `available` is -formatted with the token decimals, while `availableRaw` keeps the raw integer -value. `decimals` is exposed as `Int?`. `selection` preserves the +The selector receives `FeeOptionWithBalance` values. For Ethereum fees, `balance` +contains the matching `TokenBalance` when available. For both Ethereum and Solana +fees, `available` is formatted with the token decimals, while `availableRaw` keeps +the raw integer value. `decimals` is exposed as `Int?`, allowing +`FeeOptionSelector.firstAvailable` to select the first affordable option on either +network family. `selection` preserves the API-provided `tokenID` when present and falls back to the token symbol. Sponsored transactions invoke the selector with an empty list; return `null` after acknowledging the free fee, or throw to stop execution. `FeeOptionSelector.firstAvailable` returns diff --git a/oms-wallet-kotlin-sdk/api/public-api.txt b/oms-wallet-kotlin-sdk/api/public-api.txt index dd0a91e..d4341eb 100644 --- a/oms-wallet-kotlin-sdk/api/public-api.txt +++ b/oms-wallet-kotlin-sdk/api/public-api.txt @@ -2300,9 +2300,10 @@ public final class technology.polygon.omswallet.wallet.WalletClient { public static final java.lang.Object access$walletsFromAuthResponse(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.wallet.WalletAuthCompletion, long, kotlin.coroutines.Continuation); public static final boolean access$isEthereumAddress(technology.polygon.omswallet.wallet.WalletClient, java.lang.String); public static final technology.polygon.omswallet.wallet.ActiveWalletSession access$requireActiveWalletSession(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.OMSWalletOperation, boolean); - public static final java.lang.Object access$executePreparedTransaction(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.Network, java.lang.String, technology.polygon.omswallet.wallet.PreparedWalletTransaction, long, technology.polygon.omswallet.models.FeeOptionSelector, boolean, technology.polygon.omswallet.models.TransactionStatusPollingOptions, kotlin.coroutines.Continuation); + public static final java.lang.Object access$executePreparedTransaction(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.Network, technology.polygon.omswallet.SolanaNetwork, java.lang.String, technology.polygon.omswallet.wallet.PreparedWalletTransaction, long, technology.polygon.omswallet.models.FeeOptionSelector, boolean, technology.polygon.omswallet.models.TransactionStatusPollingOptions, kotlin.coroutines.Continuation); public static final java.lang.Object access$requestListAccessPage-zURRx2s(technology.polygon.omswallet.wallet.WalletClient, kotlin.UInt, java.lang.String, technology.polygon.omswallet.models.AccessGrantType, technology.polygon.omswallet.wallet.ActiveWalletSession, kotlin.coroutines.Continuation); public static final java.lang.Object access$enrichFeeOptionsWithBalances(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.Network, java.lang.String, java.util.List, kotlin.coroutines.Continuation); + public static final java.lang.Object access$enrichSolanaFeeOptionsWithBalances(technology.polygon.omswallet.wallet.WalletClient, technology.polygon.omswallet.SolanaNetwork, java.lang.String, java.util.List, kotlin.coroutines.Continuation); public static final java.lang.Object access$waitForTransactionStatus(technology.polygon.omswallet.wallet.WalletClient, java.lang.String, technology.polygon.omswallet.models.TransactionStatus, technology.polygon.omswallet.models.TransactionStatusPollingOptions, long, kotlin.coroutines.Continuation); public static final java.lang.String access$authorizeSignedRequest(technology.polygon.omswallet.wallet.WalletClient, long, boolean, java.lang.String, java.lang.String); } diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt index 9c2ee56..0f1561a 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt @@ -89,6 +89,7 @@ import technology.polygon.omswallet.models.SendTransactionRequest import technology.polygon.omswallet.models.SendTransactionResponse import technology.polygon.omswallet.models.SmartSessionGrant import technology.polygon.omswallet.models.SmartSessionGrantUsage +import technology.polygon.omswallet.models.SolanaBalance import technology.polygon.omswallet.models.TokenBalance import technology.polygon.omswallet.models.TransactionMode import technology.polygon.omswallet.models.TransactionStatus @@ -1801,6 +1802,7 @@ class WalletClient private constructor( ) executePreparedTransaction( network = network, + solanaNetwork = null, walletAddress = activeSession.walletAddress, prepared = prepared, requiredSessionRevision = activeSession.revision, @@ -1839,7 +1841,8 @@ class WalletClient private constructor( ) executePreparedTransaction( network = null, - walletAddress = null, + solanaNetwork = network, + walletAddress = activeSession.walletAddress, prepared = prepared, requiredSessionRevision = activeSession.revision, selectFeeOption = selectFeeOption, @@ -1879,6 +1882,7 @@ class WalletClient private constructor( ) executePreparedTransaction( network = network, + solanaNetwork = null, walletAddress = activeSession.walletAddress, prepared = prepared, requiredSessionRevision = activeSession.revision, @@ -2370,6 +2374,7 @@ class WalletClient private constructor( private suspend fun executePreparedTransaction( network: Network?, + solanaNetwork: SolanaNetwork?, walletAddress: String?, prepared: PreparedWalletTransaction, requiredSessionRevision: Long, @@ -2407,6 +2412,12 @@ class WalletClient private constructor( walletAddress = walletAddress, feeOptions = prepared.feeOptions, ) + } else if (solanaNetwork != null && walletAddress != null) { + enrichSolanaFeeOptionsWithBalances( + network = solanaNetwork, + walletAddress = walletAddress, + feeOptions = prepared.feeOptions, + ) } else { prepared.feeOptions.mapIndexed { index, feeOption -> FeeOptionWithBalance( @@ -2525,12 +2536,70 @@ class WalletClient private constructor( } } + private suspend fun enrichSolanaFeeOptionsWithBalances( + network: SolanaNetwork, + walletAddress: String, + feeOptions: List, + ): List { + val mintAddresses = + feeOptions + .filterNot { it.token.isNativeToken() } + .mapNotNull { it.token.contractAddress.normalizeSolanaAddress() } + .distinct() + val includesNative = feeOptions.any { it.token.isNativeToken() } + val balances = + runCatching { + indexerClient.getSolanaBalances( + walletAddress = walletAddress, + networks = listOf(network), + includeMetadata = false, + omitNativeBalances = !includesNative, + mintAddresses = mintAddresses, + ) + }.getOrNull() + val nativeBalance = + balances?.balances?.firstOrNull { balance -> + balance is SolanaBalance.Native && balance.network == network + } + val balancesByMint = + balances + ?.balances + ?.filterIsInstance() + ?.filter { it.network == network } + ?.associateBy { it.mintAddress } + .orEmpty() + + return feeOptions.mapIndexed { index, feeOption -> + val balance = + if (feeOption.token.isNativeToken()) { + nativeBalance + } else { + feeOption.token.contractAddress + .normalizeSolanaAddress() + ?.let { balancesByMint[it] } + } + val decimals = balance?.decimals ?: feeOption.token.decimals?.toInt() + FeeOptionWithBalance( + feeOption = feeOption, + selection = FeeOptionSelection(feeOption, index.toUInt()), + available = balance?.balance?.formatTokenAmount(decimals), + availableRaw = balance?.balance, + decimals = decimals, + ) + } + } + private fun String?.normalizeAddress(): String? = this ?.trim() ?.takeIf { it.isNotEmpty() } ?.lowercase() + private fun String?.normalizeSolanaAddress(): String? = + this + ?.trim() + ?.takeIf { it.isNotEmpty() } + private fun FeeToken.isNativeToken(): Boolean = type.equals("native", ignoreCase = true) || (contractAddress.isNullOrBlank() && tokenId.isNullOrBlank()) diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletTransactionTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletTransactionTest.kt index 648c924..cb57855 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletTransactionTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletTransactionTest.kt @@ -1270,6 +1270,120 @@ class WalletTransactionTest { assertEquals(TransactionStatusResolution.NotRequested, transaction.statusResolution) } + @Test + fun solanaFirstAvailableUsesIndexerBalances() = + runBlocking { + val walletAddress = "4Nd1mYQbqjVU2aR7cJNPyqW9XjHnBYvWQd7ZxYxvT6uP" + val usdcMint = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU" + enqueueJson( + """ + { + "txnId": "solana-first-available", + "status": "quoted", + "feeOptions": [ + { + "token": { + "network": "solana:devnet", + "name": "SOL", + "symbol": "SOL", + "type": "native" + }, + "value": "5000", + "displayValue": "0.000005" + }, + { + "token": { + "network": "solana:devnet", + "name": "USD Coin", + "symbol": "USDC", + "type": "spl", + "contractAddress": "$usdcMint" + }, + "value": "10000", + "displayValue": "0.01" + } + ], + "sponsored": false, + "expiresAt": "2099-01-01T00:00:00Z" + } + """.trimIndent(), + ) + enqueueJson( + """ + { + "balances": [ + { + "network": "solana:devnet", + "accountAddress": "$walletAddress", + "assetType": "native", + "name": "Solana", + "symbol": "SOL", + "decimals": 9, + "balance": "1000", + "formattedBalance": "0.000001", + "verificationStatus": "unknown", + "verificationSource": "none" + }, + { + "network": "solana:devnet", + "accountAddress": "$walletAddress", + "assetType": "fungible-token", + "tokenProgram": "spl-token", + "mintAddress": "$usdcMint", + "name": "USD Coin", + "symbol": "USDC", + "decimals": 6, + "balance": "20000", + "formattedBalance": "0.02", + "verificationStatus": "verified", + "verificationSource": "jupiter" + } + ], + "errors": [] + } + """.trimIndent(), + ) + enqueueJson("""{"status":"pending"}""") + val client = + restoredWalletClient( + nonceValue = "1710000121", + walletAddress = walletAddress, + environment = + OMSWalletEnvironment( + walletApiUrl = server.url("/v1/Waas/").toString(), + indexerGatewayUrl = server.url("/v1/IndexerGateway/").toString(), + solanaIndexerGatewayUrl = server.url("/v1/SolanaIndexerGateway/").toString(), + ), + ) + + val result = + client.sendSolanaTransfer( + network = SolanaNetwork.Devnet, + asset = "SOL", + to = "recipient", + amount = BigInteger("1000000"), + selectFeeOption = FeeOptionSelector.firstAvailable, + waitForStatus = false, + ) + val prepare = requireNotNull(server.takeRequest()) + val balances = requireNotNull(server.takeRequest()) + val execute = requireNotNull(server.takeRequest()) + + assertEquals("solana-first-available", result.txnId) + assertEquals("/v1/Waas/PrepareSolanaTransfer", prepare.target) + assertEquals("/v1/SolanaIndexerGateway/GetTokenBalancesDetails", balances.target) + assertTrue(requireNotNull(balances.body).utf8().contains("\"contractWhitelist\":[\"$usdcMint\"]")) + assertEquals( + WaasApi.Execute.encodeRequest( + ExecuteRequest( + txnId = "solana-first-available", + feeOption = WaasFeeOptionSelection(token = "USDC", index = 1u), + ), + ), + requireNotNull(execute.body).utf8(), + ) + } + private fun enqueueJson(body: String) { server.enqueue( MockResponse From 2982c484c54e16f0965b880666f47fd984fa32ec Mon Sep 17 00:00:00 2001 From: tolgahan-arikan Date: Wed, 9 Sep 2026 21:24:57 +0300 Subject: [PATCH 06/10] fix(wallet): manage import attestation policy --- README.md | 10 +++--- docs/api-groups.conf | 1 - docs/api.md | 11 ------- docs/error-contracts.md | 2 +- oms-wallet-kotlin-sdk/api/public-api.txt | 32 ++++++++----------- .../technology/polygon/omswallet/OMSWallet.kt | 11 +++---- .../polygon/omswallet/ParsedPublishableKey.kt | 24 ++++++++++---- .../omswallet/WalletImportConfiguration.kt | 22 ------------- .../polygon/omswallet/wallet/WalletClient.kt | 19 ++++++----- .../polygon/omswallet/OMSWalletTest.kt | 31 ++++++++++++++---- .../omswallet/PublicErrorContractsTest.kt | 10 +++--- .../wallet/WalletImportCryptoTest.kt | 14 -------- 12 files changed, 80 insertions(+), 107 deletions(-) delete mode 100644 oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/WalletImportConfiguration.kt diff --git a/README.md b/README.md index 182bbda..6a68e2a 100644 --- a/README.md +++ b/README.md @@ -339,18 +339,16 @@ persistent cleanup failure. ### Import a Wallet -Configure wallet import with audited AWS Nitro Enclave PCR0 measurements. The SDK rejects all-zero -debug measurements, verifies the attestation and request/response binding, and encrypts plaintext -keys locally before import. +Wallet import verifies AWS Nitro enclave attestations against measurements managed by each OMS +environment. Development uses Nitro debug mode, whose all-zero PCR0 does not identify a specific +enclave image; use only disposable test keys there. Staging and Production accept only the release +measurements shipped by the SDK. ```kotlin val omsWallet = OMSWallet( context = context, publishableKey = "your-publishable-key", - walletImport = WalletImportConfiguration( - trustedPcr0s = listOf("your-audited-48-byte-pcr0-hex"), - ), ) val imported = diff --git a/docs/api-groups.conf b/docs/api-groups.conf index 79668dc..93ba6de 100644 --- a/docs/api-groups.conf +++ b/docs/api-groups.conf @@ -12,7 +12,6 @@ technology.polygon.omswallet.models.WalletType = WalletType technology.polygon.omswallet.models.Wallet = Wallet technology.polygon.omswallet.models.WalletKeyOrigin = WalletKeyOrigin technology.polygon.omswallet.models.Page = Page -technology.polygon.omswallet.WalletImportConfiguration = WalletImportConfiguration technology.polygon.omswallet.models.WalletImportCipherSuite = WalletImportCipherSuite technology.polygon.omswallet.models.WalletImportPrivateKey = WalletImportPrivateKey technology.polygon.omswallet.models.WalletImportPrivateKey.Ethereum = WalletImportPrivateKey.Ethereum diff --git a/docs/api.md b/docs/api.md index 469ad16..990bd8c 100644 --- a/docs/api.md +++ b/docs/api.md @@ -22,7 +22,6 @@ class OMSWallet { context: Context, publishableKey: String, okHttpClient: OkHttpClient = OkHttpClient(), - walletImport: WalletImportConfiguration? = null, ) } ``` @@ -144,16 +143,6 @@ data class Page( ) ``` -### `WalletImportConfiguration` - -Trust policy used to verify attested wallet-import responses. - -```kotlin -class WalletImportConfiguration( - trustedPcr0s: Collection, -) -``` - ### `WalletImportCipherSuite` HPKE cipher suites accepted by the wallet-import transport. diff --git a/docs/error-contracts.md b/docs/error-contracts.md index 595f88c..8d85c1f 100644 --- a/docs/error-contracts.md +++ b/docs/error-contracts.md @@ -49,7 +49,7 @@ whether `upstreamError` should be present, and which tests own the contract. | `client.wallet.signOut` | Persistent session, redirect-state, or signer cleanup failure | `OMSWalletStorageException`, `OMS_STORAGE_ERROR`; in-memory session is already cleared | Keep the user signed out locally; report or retry persistent cleanup as appropriate | Absent | `WalletSessionTest` | | Protected wallet methods: `getIdToken`, signing and transaction methods, wallet import, `getTransactionStatus`, access authorization/usage/listing/revocation | Missing, expired, or stale local session | `OMSWalletSessionException` | Authenticate again or recover local session; no remote request was made | Absent | `PublicErrorContractsTest` | | Wallet auth, signing, transactions, import, and owner access methods | SDK-local validation or fee-selection failure | `OMSWalletValidationException` | Correct parameters or local fee selection; do not retry as an upstream outage | Absent | `PublicErrorContractsTest`, `WalletImportCryptoTest`, `WalletAccessTest` | -| `client.wallet.getWalletImportRecipientKey`, `importWallet`, `importEncryptedWallet` | Recipient-key attestation is missing, stale, malformed, untrusted, or does not match the request/response | `OMSWalletAttestationException`, `OMS_ATTESTATION_VERIFICATION_FAILED` | Do not encrypt or submit key material; retry only after confirming the configured PCR0 allowlist and WaaS environment | Absent | `WalletImportCryptoTest` | +| `client.wallet.getWalletImportRecipientKey`, `importWallet`, `importEncryptedWallet` | Recipient-key attestation is missing, stale, malformed, untrusted, or does not match the request/response | `OMSWalletAttestationException`, `OMS_ATTESTATION_VERIFICATION_FAILED` | Do not encrypt or submit key material; retry only after confirming the SDK's managed PCR0 trust policy and WaaS environment | Absent | `WalletImportCryptoTest` | | `client.wallet.isValidMessageSignature`, `isValidTypedDataSignature` | WaaS validation backend failure | `OMSWalletRequestException` or `OMSWalletResponseException` with validation operation | Retry based on SDK code/status; log upstream detail | Present | `PublicErrorContractsTest` | | `client.wallet.sendTransaction`, `callContract` | Execute request fails after prepare | `OMSWalletTransactionException`, `OMS_TRANSACTION_EXECUTION_UNCONFIRMED`, `retryable = false`, `txnId` | Do not blindly resend the write; preserve `txnId` and upstream detail for diagnostics | Present when execute crossed transport/upstream boundary | `PublicErrorContractsTest` | | `client.wallet.sendTransaction`, `callContract` | Submitted transaction status polling fails | `OMSWalletTransactionException`, `OMS_TRANSACTION_STATUS_LOOKUP_FAILED`, `retryable = true`, `txnId` | Retry status lookup, not the original write | Present when polling crossed transport/upstream boundary | `PublicErrorContractsTest` | diff --git a/oms-wallet-kotlin-sdk/api/public-api.txt b/oms-wallet-kotlin-sdk/api/public-api.txt index d4341eb..ff08e20 100644 --- a/oms-wallet-kotlin-sdk/api/public-api.txt +++ b/oms-wallet-kotlin-sdk/api/public-api.txt @@ -55,15 +55,15 @@ public final class technology.polygon.omswallet.OMSWallet { public static final technology.polygon.omswallet.OMSWallet$Companion Companion; public final technology.polygon.omswallet.wallet.WalletClient getWallet(); public final technology.polygon.omswallet.indexer.IndexerClient getIndexer(); - public technology.polygon.omswallet.OMSWallet(android.content.Context, java.lang.String, okhttp3.OkHttpClient, technology.polygon.omswallet.WalletImportConfiguration); - public technology.polygon.omswallet.OMSWallet(android.content.Context, java.lang.String, okhttp3.OkHttpClient, technology.polygon.omswallet.WalletImportConfiguration, int, kotlin.jvm.internal.DefaultConstructorMarker); - public technology.polygon.omswallet.OMSWallet(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, okhttp3.OkHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, technology.polygon.omswallet.wallet.CredentialSigner, java.lang.String, technology.polygon.omswallet.WalletImportConfiguration, kotlin.jvm.internal.DefaultConstructorMarker); + public technology.polygon.omswallet.OMSWallet(android.content.Context, java.lang.String, okhttp3.OkHttpClient); + public technology.polygon.omswallet.OMSWallet(android.content.Context, java.lang.String, okhttp3.OkHttpClient, int, kotlin.jvm.internal.DefaultConstructorMarker); + public technology.polygon.omswallet.OMSWallet(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, okhttp3.OkHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, technology.polygon.omswallet.wallet.CredentialSigner, java.lang.String, java.util.Set, kotlin.jvm.internal.DefaultConstructorMarker); } Compiled from "OMSWallet.kt" public final class technology.polygon.omswallet.OMSWallet$Companion { - public final technology.polygon.omswallet.OMSWallet createForTesting$oms_wallet_kotlin_sdk(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, okhttp3.OkHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, technology.polygon.omswallet.wallet.CredentialSigner, java.lang.String, technology.polygon.omswallet.WalletImportConfiguration); - public static technology.polygon.omswallet.OMSWallet createForTesting$oms_wallet_kotlin_sdk$default(technology.polygon.omswallet.OMSWallet$Companion, java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, okhttp3.OkHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, technology.polygon.omswallet.wallet.CredentialSigner, java.lang.String, technology.polygon.omswallet.WalletImportConfiguration, int, java.lang.Object); + public final technology.polygon.omswallet.OMSWallet createForTesting$oms_wallet_kotlin_sdk(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, okhttp3.OkHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, technology.polygon.omswallet.wallet.CredentialSigner, java.lang.String, java.util.Set); + public static technology.polygon.omswallet.OMSWallet createForTesting$oms_wallet_kotlin_sdk$default(technology.polygon.omswallet.OMSWallet$Companion, java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, okhttp3.OkHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, technology.polygon.omswallet.wallet.CredentialSigner, java.lang.String, java.util.Set, int, java.lang.Object); public final java.lang.String scopedSessionFileName$oms_wallet_kotlin_sdk(java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment); public final java.lang.String scopedCredentialKeyAlias$oms_wallet_kotlin_sdk(java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment); public final java.lang.String scopedCredentialNonceStoreName$oms_wallet_kotlin_sdk(java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment); @@ -334,18 +334,20 @@ public final class technology.polygon.omswallet.OMSWalletValidationException ext Compiled from "ParsedPublishableKey.kt" public final class technology.polygon.omswallet.ParsedPublishableKey { - public technology.polygon.omswallet.ParsedPublishableKey(java.lang.String, java.lang.String, java.lang.String, java.lang.String); - public technology.polygon.omswallet.ParsedPublishableKey(java.lang.String, java.lang.String, java.lang.String, java.lang.String, int, kotlin.jvm.internal.DefaultConstructorMarker); + public technology.polygon.omswallet.ParsedPublishableKey(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.util.Set); + public technology.polygon.omswallet.ParsedPublishableKey(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.util.Set, int, kotlin.jvm.internal.DefaultConstructorMarker); public final java.lang.String getProjectId(); public final java.lang.String getWalletApiUrl(); public final java.lang.String getIndexerGatewayUrl(); public final java.lang.String getSolanaIndexerGatewayUrl(); + public final java.util.Set getWalletImportTrustedPcr0s(); public final java.lang.String component1(); public final java.lang.String component2(); public final java.lang.String component3(); public final java.lang.String component4(); - public final technology.polygon.omswallet.ParsedPublishableKey copy(java.lang.String, java.lang.String, java.lang.String, java.lang.String); - public static technology.polygon.omswallet.ParsedPublishableKey copy$default(technology.polygon.omswallet.ParsedPublishableKey, java.lang.String, java.lang.String, java.lang.String, java.lang.String, int, java.lang.Object); + public final java.util.Set component5(); + public final technology.polygon.omswallet.ParsedPublishableKey copy(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.util.Set); + public static technology.polygon.omswallet.ParsedPublishableKey copy$default(technology.polygon.omswallet.ParsedPublishableKey, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.util.Set, int, java.lang.Object); public java.lang.String toString(); public int hashCode(); public boolean equals(java.lang.Object); @@ -373,12 +375,6 @@ public final class technology.polygon.omswallet.SolanaNetworks { public final technology.polygon.omswallet.SolanaNetwork getMAINNET(); } -Compiled from "WalletImportConfiguration.kt" -public final class technology.polygon.omswallet.WalletImportConfiguration { - public technology.polygon.omswallet.WalletImportConfiguration(java.util.Collection); - public final java.util.Set getTrustedPcr0s$oms_wallet_kotlin_sdk(); -} - Compiled from "IndexerClient.kt" public final class technology.polygon.omswallet.indexer.IndexerClient { public static final technology.polygon.omswallet.indexer.IndexerClient$Companion Companion; @@ -2264,7 +2260,7 @@ public final class technology.polygon.omswallet.wallet.WalletClient { public static java.lang.Object getIdToken-K5VMiEY$default(technology.polygon.omswallet.wallet.WalletClient, kotlin.UInt, java.util.Map, kotlin.coroutines.Continuation, int, java.lang.Object); public final java.lang.Object revokeAccess(java.lang.String, java.lang.String, kotlin.coroutines.Continuation); public static java.lang.Object revokeAccess$default(technology.polygon.omswallet.wallet.WalletClient, java.lang.String, java.lang.String, kotlin.coroutines.Continuation, int, java.lang.Object); - public technology.polygon.omswallet.wallet.WalletClient(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, technology.polygon.omswallet.network.OMSWalletHttpClient, technology.polygon.omswallet.wallet.WalletScopeRuntime, kotlin.jvm.functions.Function0, long, int, long, long, kotlin.jvm.functions.Function2, technology.polygon.omswallet.WalletImportConfiguration, kotlin.jvm.internal.DefaultConstructorMarker); + public technology.polygon.omswallet.wallet.WalletClient(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, technology.polygon.omswallet.network.OMSWalletHttpClient, technology.polygon.omswallet.wallet.WalletScopeRuntime, kotlin.jvm.functions.Function0, long, int, long, long, kotlin.jvm.functions.Function2, java.util.Set, kotlin.jvm.internal.DefaultConstructorMarker); public static final int access$requireWaasSessionLifetimeSeconds-OGnWXxg(technology.polygon.omswallet.wallet.WalletClient, long); public static final technology.polygon.omswallet.wallet.WaasWalletGateway access$getGateway$p(technology.polygon.omswallet.wallet.WalletClient); public static final technology.polygon.omswallet.wallet.WalletScopeRuntime access$getRuntime$p(technology.polygon.omswallet.wallet.WalletClient); @@ -2310,8 +2306,8 @@ public final class technology.polygon.omswallet.wallet.WalletClient { Compiled from "WalletClient.kt" public final class technology.polygon.omswallet.wallet.WalletClient$Companion { - public final technology.polygon.omswallet.wallet.WalletClient create$oms_wallet_kotlin_sdk(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, technology.polygon.omswallet.network.OMSWalletHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, kotlin.jvm.functions.Function0, technology.polygon.omswallet.wallet.CredentialSigner, long, int, long, long, kotlin.jvm.functions.Function2, technology.polygon.omswallet.wallet.SessionExpiryScheduler, technology.polygon.omswallet.wallet.SessionExpiryDispatcher, kotlin.jvm.functions.Function0, java.lang.String, technology.polygon.omswallet.WalletImportConfiguration); - public static technology.polygon.omswallet.wallet.WalletClient create$oms_wallet_kotlin_sdk$default(technology.polygon.omswallet.wallet.WalletClient$Companion, java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, technology.polygon.omswallet.network.OMSWalletHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, kotlin.jvm.functions.Function0, technology.polygon.omswallet.wallet.CredentialSigner, long, int, long, long, kotlin.jvm.functions.Function2, technology.polygon.omswallet.wallet.SessionExpiryScheduler, technology.polygon.omswallet.wallet.SessionExpiryDispatcher, kotlin.jvm.functions.Function0, java.lang.String, technology.polygon.omswallet.WalletImportConfiguration, int, java.lang.Object); + public final technology.polygon.omswallet.wallet.WalletClient create$oms_wallet_kotlin_sdk(java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, technology.polygon.omswallet.network.OMSWalletHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, kotlin.jvm.functions.Function0, technology.polygon.omswallet.wallet.CredentialSigner, long, int, long, long, kotlin.jvm.functions.Function2, technology.polygon.omswallet.wallet.SessionExpiryScheduler, technology.polygon.omswallet.wallet.SessionExpiryDispatcher, kotlin.jvm.functions.Function0, java.lang.String, java.util.Set); + public static technology.polygon.omswallet.wallet.WalletClient create$oms_wallet_kotlin_sdk$default(technology.polygon.omswallet.wallet.WalletClient$Companion, java.lang.String, java.lang.String, technology.polygon.omswallet.network.OMSWalletEnvironment, technology.polygon.omswallet.network.OMSWalletHttpClient, technology.polygon.omswallet.session.OMSWalletSession, technology.polygon.omswallet.storage.OMSWalletSessionMetadataStore, technology.polygon.omswallet.wallet.OidcRedirectAuthStore, kotlin.jvm.functions.Function0, technology.polygon.omswallet.wallet.CredentialSigner, long, int, long, long, kotlin.jvm.functions.Function2, technology.polygon.omswallet.wallet.SessionExpiryScheduler, technology.polygon.omswallet.wallet.SessionExpiryDispatcher, kotlin.jvm.functions.Function0, java.lang.String, java.util.Set, int, java.lang.Object); public technology.polygon.omswallet.wallet.WalletClient$Companion(kotlin.jvm.internal.DefaultConstructorMarker); } diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWallet.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWallet.kt index 4aca7f4..2ac455f 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWallet.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/OMSWallet.kt @@ -32,7 +32,7 @@ class OMSWallet private constructor( oidcRedirectAuthStore: OidcRedirectAuthStore?, credentialSigner: CredentialSigner?, projectScopeKey: String?, - walletImport: WalletImportConfiguration?, + walletImportTrustedPcr0s: Set?, ) { private val resolvedProjectId: String = projectId ?: parsePublishableKey(publishableKey).projectId private val resolvedEnvironment: OMSWalletEnvironment = @@ -51,7 +51,7 @@ class OMSWallet private constructor( oidcRedirectAuthStore = oidcRedirectAuthStore, credentialSigner = credentialSigner, projectScopeKey = projectScopeKey, - walletImport = walletImport, + walletImportTrustedPcr0s = walletImportTrustedPcr0s, ) val indexer: IndexerClient = @@ -77,7 +77,6 @@ class OMSWallet private constructor( context: Context, publishableKey: String, okHttpClient: OkHttpClient = OkHttpClient(), - walletImport: WalletImportConfiguration? = null, ) : this( publishableKey = publishableKey, projectId = projectIdFromPublishableKey(publishableKey), @@ -101,7 +100,7 @@ class OMSWallet private constructor( nonceStoreName = scopedCredentialNonceStoreName(publishableKey), ), projectScopeKey = scopedSessionSuffix(publishableKey), - walletImport = walletImport, + walletImportTrustedPcr0s = parsePublishableKey(publishableKey).walletImportTrustedPcr0s, ) companion object { @@ -116,7 +115,7 @@ class OMSWallet private constructor( oidcRedirectAuthStore: OidcRedirectAuthStore? = null, credentialSigner: CredentialSigner? = null, projectScopeKey: String? = null, - walletImport: WalletImportConfiguration? = null, + walletImportTrustedPcr0s: Set? = null, ): OMSWallet = OMSWallet( publishableKey = publishableKey, @@ -128,7 +127,7 @@ class OMSWallet private constructor( oidcRedirectAuthStore = oidcRedirectAuthStore, credentialSigner = credentialSigner, projectScopeKey = projectScopeKey, - walletImport = walletImport, + walletImportTrustedPcr0s = walletImportTrustedPcr0s, ) private fun projectIdFromPublishableKey(publishableKey: String): String = parsePublishableKey(publishableKey).projectId diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/ParsedPublishableKey.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/ParsedPublishableKey.kt index 43fd7f0..eaebcff 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/ParsedPublishableKey.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/ParsedPublishableKey.kt @@ -8,6 +8,7 @@ internal data class ParsedPublishableKey( val walletApiUrl: String, val indexerGatewayUrl: String, val solanaIndexerGatewayUrl: String = "${walletApiUrl.trimEnd('/')}/v1/SolanaIndexerGateway/", + val walletImportTrustedPcr0s: Set, ) /** @@ -29,22 +30,33 @@ internal fun parsePublishableKey(publishableKey: String): ParsedPublishableKey { walletApiUrl = route.apiUrl, indexerGatewayUrl = "${route.apiUrl}/v1/IndexerGateway/", solanaIndexerGatewayUrl = "${route.apiUrl}/v1/SolanaIndexerGateway/", + walletImportTrustedPcr0s = route.walletImportTrustedPcr0s, ) } private data class PublishableKeyRoute( val prefix: String, val apiUrl: String, + val walletImportTrustedPcr0s: Set, ) +// Staging and Production measurements come from the corresponding WaaS GitHub releases. During +// rotation, publish an SDK that trusts both the current and replacement measurements before the +// replacement enclave is deployed, then remove the retired measurement in a later SDK release. +private val debugWalletImportPcr0s = setOf("0".repeat(96)) +private val stagingWalletImportPcr0s = + setOf("e4da1f70f6e781d7196dff36d21e57bb5603ec4bcacefb7061493049292b76b620b0ad23b82e280d6130f67384051e9f") +private val productionWalletImportPcr0s = + setOf("671f22183eed852f4051a50ee54b45153499501538cbd64a277b8ff22a012b37f1905ebfcf7a6be8ce00ec0c8db7bbd2") + private val publishableKeyRoutes = listOf( - PublishableKeyRoute("pk_dev_sdbx_", "https://sandbox-api.dev.polygon-dev.technology"), - PublishableKeyRoute("pk_dev_live_", "https://api.dev.polygon-dev.technology"), - PublishableKeyRoute("pk_stg_sdbx_", "https://sandbox-api.stg.polygon-dev.technology"), - PublishableKeyRoute("pk_stg_live_", "https://api.stg.polygon-dev.technology"), - PublishableKeyRoute("pk_sdbx_", "https://sandbox-api.polygon.technology"), - PublishableKeyRoute("pk_live_", "https://api.polygon.technology"), + PublishableKeyRoute("pk_dev_sdbx_", "https://sandbox-api.dev.polygon-dev.technology", debugWalletImportPcr0s), + PublishableKeyRoute("pk_dev_live_", "https://api.dev.polygon-dev.technology", debugWalletImportPcr0s), + PublishableKeyRoute("pk_stg_sdbx_", "https://sandbox-api.stg.polygon-dev.technology", stagingWalletImportPcr0s), + PublishableKeyRoute("pk_stg_live_", "https://api.stg.polygon-dev.technology", stagingWalletImportPcr0s), + PublishableKeyRoute("pk_sdbx_", "https://sandbox-api.polygon.technology", productionWalletImportPcr0s), + PublishableKeyRoute("pk_live_", "https://api.polygon.technology", productionWalletImportPcr0s), ) private fun invalidPublishableKey(): OMSWalletValidationException = OMSWalletValidationException(message = "Invalid publishableKey.") diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/WalletImportConfiguration.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/WalletImportConfiguration.kt deleted file mode 100644 index 0c3875f..0000000 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/WalletImportConfiguration.kt +++ /dev/null @@ -1,22 +0,0 @@ -package technology.polygon.omswallet - -/** Trust policy used to verify attested wallet-import responses. */ -class WalletImportConfiguration( - trustedPcr0s: Collection, -) { - internal val trustedPcr0s: Set = - trustedPcr0s - .map { it.trim().lowercase().removePrefix("0x") } - .also { normalized -> - require( - normalized.isNotEmpty() && - normalized.all { value -> - value.length == 96 && - value.all { it in '0'..'9' || it in 'a'..'f' } && - value.any { it != '0' } - }, - ) { - "walletImport.trustedPcr0s must contain at least one nonzero 48-byte hex PCR0" - } - }.toSet() -} diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt index 0f1561a..4cadc8a 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt @@ -25,7 +25,6 @@ import technology.polygon.omswallet.OMSWalletStorageException import technology.polygon.omswallet.OMSWalletTransactionException import technology.polygon.omswallet.OMSWalletValidationException import technology.polygon.omswallet.SolanaNetwork -import technology.polygon.omswallet.WalletImportConfiguration import technology.polygon.omswallet.indexer.IndexerClient import technology.polygon.omswallet.internal.generated.waas.AuthMode import technology.polygon.omswallet.internal.generated.waas.AuthorizeRemoteAccessRequest @@ -198,7 +197,7 @@ class WalletClient private constructor( private val transactionStatusPollIntervalMillis: Long, private val transactionStatusPollTimeoutMillis: Long, private val transactionStatusDelay: suspend (Long) -> Unit, - private val walletImport: WalletImportConfiguration?, + private val walletImportTrustedPcr0s: Set?, ) { companion object { /** @@ -231,7 +230,7 @@ class WalletClient private constructor( sessionExpiryDispatcher: SessionExpiryDispatcher = AndroidMainThreadSessionExpiryDispatcher, now: () -> Long = OMSWalletTimestamps::nowMilliseconds, projectScopeKey: String? = null, - walletImport: WalletImportConfiguration? = null, + walletImportTrustedPcr0s: Set? = null, ): WalletClient { val createRuntime = { WalletScopeRuntime( @@ -260,7 +259,7 @@ class WalletClient private constructor( transactionStatusPollIntervalMillis = transactionStatusPollIntervalMillis, transactionStatusPollTimeoutMillis = transactionStatusPollTimeoutMillis, transactionStatusDelay = transactionStatusDelay, - walletImport = walletImport, + walletImportTrustedPcr0s = walletImportTrustedPcr0s, ) } } @@ -275,7 +274,7 @@ class WalletClient private constructor( environment = environment, transport = transport, authorizeSignedRequest = ::authorizeSignedRequest, - walletImport = walletImport, + walletImportTrustedPcr0s = walletImportTrustedPcr0s, ) private val indexerClient: IndexerClient = IndexerClient.create( @@ -2772,7 +2771,7 @@ private class WaasWalletGateway( endpoint: String, body: String, ) -> String, - private val walletImport: WalletImportConfiguration?, + private val walletImportTrustedPcr0s: Set?, ) { private val publicClient: WaasPublicClient = WaasPublicClient( @@ -3296,16 +3295,16 @@ private class WaasWalletGateway( ) private fun walletImportClient(requiredSessionRevision: Long): WaasClient { - require(walletImport != null) { "Wallet import requires walletImport.trustedPcr0s configuration" } + require(walletImportTrustedPcr0s != null) { "Wallet import is unavailable for this WaaS environment" } return WaasClient( baseUrl = environment.walletApiBaseUrl(), - transport = attestedSignedTransport(requiredSessionRevision, walletImport), + transport = attestedSignedTransport(requiredSessionRevision, walletImportTrustedPcr0s), ) } private fun attestedSignedTransport( requiredSessionRevision: Long, - configuration: WalletImportConfiguration, + trustedPcr0s: Set, ): LambdaWebRpcTransport = LambdaWebRpcTransport { baseUrl, path, body, headers -> val endpoint = resolveEndpoint(path) @@ -3339,7 +3338,7 @@ private class WaasWalletGateway( requestBody = body, responseBody = response.body, nonce = nonce, - trustedPcr0s = configuration.trustedPcr0s, + trustedPcr0s = trustedPcr0s, ) WebRpcHttpResponse(response.statusCode, response.body) } diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/OMSWalletTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/OMSWalletTest.kt index b540db5..94705ce 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/OMSWalletTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/OMSWalletTest.kt @@ -21,20 +21,37 @@ class OMSWalletTest { fun parsePublishableKeyDerivesProjectAndServiceUrls() { val cases = listOf( - "pk_dev_sdbx_project_key" to "https://sandbox-api.dev.polygon-dev.technology", - "pk_dev_live_project_key" to "https://api.dev.polygon-dev.technology", - "pk_stg_sdbx_project_key" to "https://sandbox-api.stg.polygon-dev.technology", - "pk_stg_live_project_key" to "https://api.stg.polygon-dev.technology", - "pk_sdbx_project_key" to "https://sandbox-api.polygon.technology", - "pk_live_project_key" to "https://api.polygon.technology", + Triple("pk_dev_sdbx_project_key", "https://sandbox-api.dev.polygon-dev.technology", "0".repeat(96)), + Triple("pk_dev_live_project_key", "https://api.dev.polygon-dev.technology", "0".repeat(96)), + Triple( + "pk_stg_sdbx_project_key", + "https://sandbox-api.stg.polygon-dev.technology", + "e4da1f70f6e781d7196dff36d21e57bb5603ec4bcacefb7061493049292b76b620b0ad23b82e280d6130f67384051e9f", + ), + Triple( + "pk_stg_live_project_key", + "https://api.stg.polygon-dev.technology", + "e4da1f70f6e781d7196dff36d21e57bb5603ec4bcacefb7061493049292b76b620b0ad23b82e280d6130f67384051e9f", + ), + Triple( + "pk_sdbx_project_key", + "https://sandbox-api.polygon.technology", + "671f22183eed852f4051a50ee54b45153499501538cbd64a277b8ff22a012b37f1905ebfcf7a6be8ce00ec0c8db7bbd2", + ), + Triple( + "pk_live_project_key", + "https://api.polygon.technology", + "671f22183eed852f4051a50ee54b45153499501538cbd64a277b8ff22a012b37f1905ebfcf7a6be8ce00ec0c8db7bbd2", + ), ) - cases.forEach { (publishableKey, apiUrl) -> + cases.forEach { (publishableKey, apiUrl, walletImportPcr0) -> assertEquals( ParsedPublishableKey( projectId = "prj_project", walletApiUrl = apiUrl, indexerGatewayUrl = "$apiUrl/v1/IndexerGateway/", + walletImportTrustedPcr0s = setOf(walletImportPcr0), ), parsePublishableKey(publishableKey), ) diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt index f9e7762..b65afc5 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt @@ -98,7 +98,7 @@ class PublicErrorContractsTest { ) val client = createOmsClientWithSession( - walletImport = WalletImportConfiguration(listOf("a".repeat(96))), + walletImportTrustedPcr0s = setOf("a".repeat(96)), ) assertEquals( @@ -1160,7 +1160,7 @@ class PublicErrorContractsTest { okHttpClient: OkHttpClient = OkHttpClient(), oidcRedirectAuthStore: OidcRedirectAuthStore? = InMemoryOidcRedirectAuthStore(), credentialSigner: CredentialSigner = TrackingCredentialSigner(), - walletImport: WalletImportConfiguration? = null, + walletImportTrustedPcr0s: Set? = null, ): OMSWallet = OMSWallet.createForTesting( publishableKey = "test-publishable-key", @@ -1170,14 +1170,14 @@ class PublicErrorContractsTest { sessionStore = InMemorySessionStore(), oidcRedirectAuthStore = oidcRedirectAuthStore, credentialSigner = credentialSigner, - walletImport = walletImport, + walletImportTrustedPcr0s = walletImportTrustedPcr0s, ) private fun createOmsClientWithSession( okHttpClient: OkHttpClient = OkHttpClient(), - walletImport: WalletImportConfiguration? = null, + walletImportTrustedPcr0s: Set? = null, ): OMSWallet = - createOmsClient(okHttpClient = okHttpClient, walletImport = walletImport).also { client -> + createOmsClient(okHttpClient = okHttpClient, walletImportTrustedPcr0s = walletImportTrustedPcr0s).also { client -> client.wallet.restoreSession(activeSessionSnapshot()) } diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletImportCryptoTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletImportCryptoTest.kt index 05a0960..7f17ba2 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletImportCryptoTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/wallet/WalletImportCryptoTest.kt @@ -10,24 +10,10 @@ import org.junit.Assert.assertTrue import org.junit.Test import technology.polygon.omswallet.OMSWalletAttestationException import technology.polygon.omswallet.OMSWalletErrorCode -import technology.polygon.omswallet.WalletImportConfiguration import technology.polygon.omswallet.models.WalletImportPrivateKey import java.security.MessageDigest class WalletImportCryptoTest { - @Test - fun configurationRejectsMalformedAndAllZeroPcr0s() { - listOf( - emptyList(), - listOf("0".repeat(95)), - listOf("0".repeat(96)), - listOf("z".repeat(96)), - ).forEach { values -> - assertThrows(IllegalArgumentException::class.java) { WalletImportConfiguration(values) } - } - WalletImportConfiguration(listOf("0x" + "a".repeat(96))) - } - @Test fun privateKeyValidationCoversScalarAndLengthBoundaries() { val one = ByteArray(32).also { it[31] = 1 } From b74a6ddbe9f493c1facb683a409c8045ba00bde3 Mon Sep 17 00:00:00 2001 From: tolgahan-arikan Date: Wed, 16 Sep 2026 21:08:09 +0300 Subject: [PATCH 07/10] Update WaaS deployment metadata --- .../technology/polygon/omswallet/samples/DemoConfig.kt | 2 +- .../polygon/omswallet/ParsedPublishableKey.kt | 10 +++++----- .../internal/generated/waas/WaasWalletClient.kt | 6 +++--- .../java/technology/polygon/omswallet/OMSWalletTest.kt | 8 ++++---- .../omswallet/trailsactions/TrailsActionsActivity.kt | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/technology/polygon/omswallet/samples/DemoConfig.kt b/app/src/main/java/technology/polygon/omswallet/samples/DemoConfig.kt index 460e96a..cc8c177 100644 --- a/app/src/main/java/technology/polygon/omswallet/samples/DemoConfig.kt +++ b/app/src/main/java/technology/polygon/omswallet/samples/DemoConfig.kt @@ -1,7 +1,7 @@ package technology.polygon.omswallet.samples internal object DemoConfig { - const val demoPublishableKey: String = "pk_sdbx_01kqfw9zaykks_01kwetq606fv699qb9bhfmb45s" + const val demoPublishableKey: String = "pk_sdbx_01m2mwxcn8p59_01m2n33tshe52vt5jyjyt0kc6g" const val demoGoogleWebClientId: String = "970987756660-0dh5gubqfiugm452raf7mm39qaq639hn.apps.googleusercontent.com" const val googleIssuer: String = "https://accounts.google.com" diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/ParsedPublishableKey.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/ParsedPublishableKey.kt index eaebcff..8d1ec0d 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/ParsedPublishableKey.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/ParsedPublishableKey.kt @@ -40,14 +40,14 @@ private data class PublishableKeyRoute( val walletImportTrustedPcr0s: Set, ) -// Staging and Production measurements come from the corresponding WaaS GitHub releases. During -// rotation, publish an SDK that trusts both the current and replacement measurements before the -// replacement enclave is deployed, then remove the retired measurement in a later SDK release. +// Measurements are pinned to the deployed WaaS builds. Production measurements are published in +// WaaS GitHub releases; Staging can advance between releases. During rotation, publish an SDK that +// trusts both measurements before deploying the replacement, then remove the retired measurement. private val debugWalletImportPcr0s = setOf("0".repeat(96)) private val stagingWalletImportPcr0s = - setOf("e4da1f70f6e781d7196dff36d21e57bb5603ec4bcacefb7061493049292b76b620b0ad23b82e280d6130f67384051e9f") + setOf("e271fe4b26c9d58d6089b908ab713f888e6107e2cb4782ddaceea950bbec9971ccd9159e7a099bd506e04ce55c3da696") private val productionWalletImportPcr0s = - setOf("671f22183eed852f4051a50ee54b45153499501538cbd64a277b8ff22a012b37f1905ebfcf7a6be8ce00ec0c8db7bbd2") + setOf("1935cbc713f0b43060315689e87285f6ba76bcf06f26d0719735e8d674b71e0eff71dcf77fe90ab32870ef3c954973b7") private val publishableKeyRoutes = listOf( diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/internal/generated/waas/WaasWalletClient.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/internal/generated/waas/WaasWalletClient.kt index 67ee410..ad19fa8 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/internal/generated/waas/WaasWalletClient.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/internal/generated/waas/WaasWalletClient.kt @@ -15,7 +15,7 @@ import kotlinx.serialization.json.JsonElement import kotlinx.coroutines.CancellationException import java.io.IOException -// waas v1-26.8.24-262bd7ba 688ad6c684fc1ffa8aab10d476c7fb2dd60a5019 +// waas v1-26.9.9-42835a0a 2adcb9fa810bf56d76cec4c246c328e3ccad75de // -- // Code generated by webrpc-gen@v0.37.2 with github.com/webrpc/gen-kotlin@v0.3.2 generator. DO NOT EDIT. // @@ -25,10 +25,10 @@ import java.io.IOException const val WEBRPC_VERSION = "v1" // Schema version of your RIDL schema -const val WEBRPC_SCHEMA_VERSION = "v1-26.8.24-262bd7ba" +const val WEBRPC_SCHEMA_VERSION = "v1-26.9.9-42835a0a" // Schema hash generated from your RIDL schema -const val WEBRPC_SCHEMA_HASH = "688ad6c684fc1ffa8aab10d476c7fb2dd60a5019" +const val WEBRPC_SCHEMA_HASH = "2adcb9fa810bf56d76cec4c246c328e3ccad75de" // region Types diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/OMSWalletTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/OMSWalletTest.kt index 94705ce..ddf59b8 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/OMSWalletTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/OMSWalletTest.kt @@ -26,22 +26,22 @@ class OMSWalletTest { Triple( "pk_stg_sdbx_project_key", "https://sandbox-api.stg.polygon-dev.technology", - "e4da1f70f6e781d7196dff36d21e57bb5603ec4bcacefb7061493049292b76b620b0ad23b82e280d6130f67384051e9f", + "e271fe4b26c9d58d6089b908ab713f888e6107e2cb4782ddaceea950bbec9971ccd9159e7a099bd506e04ce55c3da696", ), Triple( "pk_stg_live_project_key", "https://api.stg.polygon-dev.technology", - "e4da1f70f6e781d7196dff36d21e57bb5603ec4bcacefb7061493049292b76b620b0ad23b82e280d6130f67384051e9f", + "e271fe4b26c9d58d6089b908ab713f888e6107e2cb4782ddaceea950bbec9971ccd9159e7a099bd506e04ce55c3da696", ), Triple( "pk_sdbx_project_key", "https://sandbox-api.polygon.technology", - "671f22183eed852f4051a50ee54b45153499501538cbd64a277b8ff22a012b37f1905ebfcf7a6be8ce00ec0c8db7bbd2", + "1935cbc713f0b43060315689e87285f6ba76bcf06f26d0719735e8d674b71e0eff71dcf77fe90ab32870ef3c954973b7", ), Triple( "pk_live_project_key", "https://api.polygon.technology", - "671f22183eed852f4051a50ee54b45153499501538cbd64a277b8ff22a012b37f1905ebfcf7a6be8ce00ec0c8db7bbd2", + "1935cbc713f0b43060315689e87285f6ba76bcf06f26d0719735e8d674b71e0eff71dcf77fe90ab32870ef3c954973b7", ), ) diff --git a/trails-actions/src/main/java/technology/polygon/omswallet/trailsactions/TrailsActionsActivity.kt b/trails-actions/src/main/java/technology/polygon/omswallet/trailsactions/TrailsActionsActivity.kt index c4affbc..6ea9cd6 100644 --- a/trails-actions/src/main/java/technology/polygon/omswallet/trailsactions/TrailsActionsActivity.kt +++ b/trails-actions/src/main/java/technology/polygon/omswallet/trailsactions/TrailsActionsActivity.kt @@ -2183,6 +2183,6 @@ class TrailsActionsActivity : AppCompatActivity() { } private object DemoConfig { - const val demoPublishableKey: String = "pk_sdbx_01kqfw9zaykks_01kwetq606fv699qb9bhfmb45s" + const val demoPublishableKey: String = "pk_sdbx_01m2mwxcn8p59_01m2n33tshe52vt5jyjyt0kc6g" const val oidcRedirectUri: String = "omsclientkotlindemo://auth/callback" } From 63c1f6df9ac7cf7c6c92fbb8bd377a555ddc0f49 Mon Sep 17 00:00:00 2001 From: tolgahan-arikan Date: Thu, 17 Sep 2026 13:37:48 +0300 Subject: [PATCH 08/10] chore(release): prepare 0.3.0 migration --- MIGRATION.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 3 ++ publishing.md | 4 ++- 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 MIGRATION.md diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..a071f14 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,79 @@ +# Migration Guide + +This document records breaking changes and the steps to migrate between published +versions of `io.github.0xsequence:oms-wallet-kotlin-sdk`. + +## 0.3.0 + +### Wallet types and key origin + +`WalletType` now includes `Solana`. Update exhaustive `when` expressions to handle Solana wallets +before passing wallet addresses or messages to Ethereum-only code. + +Every `Wallet` now has a required `keyOrigin`. Wallets returned by the SDK already include it. +Tests, mocks, or adapters that construct `Wallet` values directly must pass +`WalletKeyOrigin.Enclave` or `WalletKeyOrigin.Imported`: + +```kotlin +val wallet = + Wallet( + id = "wallet-id", + type = WalletType.Ethereum, + address = "0x1111111111111111111111111111111111111111", + keyOrigin = WalletKeyOrigin.Enclave, + ) +``` + +### Access grants and revocation + +`CredentialInfo` was renamed to `WalletCredential`. Access listing now distinguishes direct +credentials from remote smart sessions: + +- `listAccess()` returns `List` instead of `List`. +- `ListAccessResponse` was replaced by `AccessGrantPage`. +- `listAccessPage()` and `listAccessPages()` return access-grant pages. + +Narrow on each grant before reading remote-session fields: + +```kotlin +for (grant in omsWallet.wallet.listAccess()) { + when (grant) { + is AccessGrant.Direct -> println(grant.credential.credentialId) + is AccessGrant.Remote -> { + // Display the remote app/session and its authorized permissions. + println("${grant.sessionId} ${grant.metadata} ${grant.grants}") + } + } +} +``` + +The public `revokeAccess` parameter changed from `targetCredentialId` to `credentialId`. Pass an +optional `sessionId` to revoke only one remote session for that credential: + +```kotlin +// 0.2.0 +omsWallet.wallet.revokeAccess(targetCredentialId = credentialId) + +// 0.3.0 +omsWallet.wallet.revokeAccess(credentialId = credentialId) +omsWallet.wallet.revokeAccess( + credentialId = credentialId, + sessionId = sessionId, +) +``` + +Authentication results and pending wallet selections expose `WalletCredential` through their +existing `credential` property. + +### Sponsored fee selectors + +Fee selections can now include the quoted option index. Custom selectors should return the +provided option's `selection` value instead of reconstructing a selection from its token: + +```kotlin +val selector = FeeOptionSelector { options -> options.firstOrNull()?.selection } +``` + +Sponsored transactions invoke the selector with an empty list. Return `null` to acknowledge the +free fee, or throw to stop execution. `FeeOptionSelector.firstAvailable` already handles both +sponsored and non-sponsored transactions. diff --git a/README.md b/README.md index 6a68e2a..00ba4c8 100644 --- a/README.md +++ b/README.md @@ -628,6 +628,9 @@ val result = ## Reference +When upgrading from `0.2.0`, see [MIGRATION.md](./MIGRATION.md) for the breaking changes in +`0.3.0`. + ### Errors Public SDK APIs throw `OMSWalletException` subclasses with stable fields such as diff --git a/publishing.md b/publishing.md index a593a7d..5e3ce56 100644 --- a/publishing.md +++ b/publishing.md @@ -21,7 +21,9 @@ Prerequisites: ``` 2. Update `POM_VERSION_NAME` in `gradle.properties`. Update `README.md` and - `docs/api.md` too if the release changes public behavior or API docs. + `docs/api.md` too if the release changes public behavior or API docs. If the + release contains breaking changes, update `MIGRATION.md` with steps from the + previous published version. 3. Verify the SDK: From 130d0b41c3ebcdc1129936a15c7c0933593d001b Mon Sep 17 00:00:00 2001 From: tolgahan-arikan Date: Thu, 17 Sep 2026 15:36:47 +0300 Subject: [PATCH 09/10] docs: clarify 0.3.0 migration guidance --- MIGRATION.md | 58 ++++++++++++++++--- README.md | 43 +++++++++----- docs/api.md | 9 ++- docs/error-contracts.md | 2 +- .../polygon/omswallet/wallet/WalletClient.kt | 13 +++-- .../omswallet/PublicErrorContractsTest.kt | 23 ++++++++ 6 files changed, 121 insertions(+), 27 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index a071f14..d4d501a 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -24,6 +24,20 @@ val wallet = ) ``` +### Error enum cases + +`OMSWalletErrorCode` now includes `AttestationVerificationFailed`. `OMSWalletOperation` now +includes these operation identifiers: + +- `WalletImportWallet`, `WalletGetImportRecipientKey`, and `WalletImportEncryptedWallet` +- `WalletInspectRemoteCredential`, `WalletAuthorizeRemoteAccess`, + `WalletGetRemoteAccessSession`, and `WalletGetRemoteAccessSessionUsage` +- `WalletSignSolanaMessage`, `WalletIsValidSolanaMessageSignature`, and + `WalletSendSolanaTransfer` +- `IndexerGetSolanaBalances` + +Update exhaustive `when` expressions over either public enum to handle the new cases. + ### Access grants and revocation `CredentialInfo` was renamed to `WalletCredential`. Access listing now distinguishes direct @@ -47,19 +61,27 @@ for (grant in omsWallet.wallet.listAccess()) { } ``` -The public `revokeAccess` parameter changed from `targetCredentialId` to `credentialId`. Pass an -optional `sessionId` to revoke only one remote session for that credential: +The public `revokeAccess` parameter changed from `targetCredentialId` to `credentialId`. For a +direct grant, omit `sessionId`. For a remote grant, its `sessionId` is required and revokes exactly +that session; revoke each session separately when a remote credential has more than one: ```kotlin // 0.2.0 omsWallet.wallet.revokeAccess(targetCredentialId = credentialId) // 0.3.0 -omsWallet.wallet.revokeAccess(credentialId = credentialId) -omsWallet.wallet.revokeAccess( - credentialId = credentialId, - sessionId = sessionId, -) +val grant = omsWallet.wallet.listAccess().firstOrNull { !it.credential.isCaller } +if (grant != null) { + when (grant) { + is AccessGrant.Direct -> + omsWallet.wallet.revokeAccess(credentialId = grant.credential.credentialId) + is AccessGrant.Remote -> + omsWallet.wallet.revokeAccess( + credentialId = grant.credential.credentialId, + sessionId = grant.sessionId, + ) + } +} ``` Authentication results and pending wallet selections expose `WalletCredential` through their @@ -74,6 +96,28 @@ provided option's `selection` value instead of reconstructing a selection from i val selector = FeeOptionSelector { options -> options.firstOrNull()?.selection } ``` +`FeeOptionWithBalance` now stores `selection` as its second constructor property. Code that creates +or destructures this data class positionally must account for the inserted property; named +arguments and property access avoid component-order mistakes: + +```kotlin +// 0.2.0 +val oldOption = FeeOptionWithBalance(feeOption, balance, available, availableRaw, decimals) +val (quotedFee, quotedBalance) = oldOption + +// 0.3.0 +val option = + FeeOptionWithBalance( + feeOption = feeOption, + balance = balance, + available = available, + availableRaw = availableRaw, + decimals = decimals, + ) +val quotedFee = option.feeOption +val quotedBalance = option.balance +``` + Sponsored transactions invoke the selector with an empty list. Return `null` to acknowledge the free fee, or throw to stop execution. `FeeOptionSelector.firstAvailable` already handles both sponsored and non-sponsored transactions. diff --git a/README.md b/README.md index 00ba4c8..771389e 100644 --- a/README.md +++ b/README.md @@ -359,8 +359,9 @@ val imported = println(imported.wallet.keyOrigin == WalletKeyOrigin.Imported) ``` -Ethereum imports accept 32 raw bytes or hexadecimal text. Solana imports accept a 32-byte seed, -64-byte keypair, or base58 text. The SDK does not persist plaintext imported keys. For +Ethereum imports accept a 32-byte raw scalar or 64 hexadecimal digits, optionally prefixed with +`0x`. Solana imports accept a 32-byte seed or 64-byte keypair as raw bytes, or the base58 encoding +of either. The SDK does not persist plaintext imported keys. For caller-managed HPKE, use `getWalletImportRecipientKey` followed by `importEncryptedWallet`; both responses remain attestation verified. @@ -600,8 +601,8 @@ contains the matching `TokenBalance` when available. For both Ethereum and Solan fees, `available` is formatted with the token decimals, while `availableRaw` keeps the raw integer value. `decimals` is exposed as `Int?`, allowing `FeeOptionSelector.firstAvailable` to select the first affordable option on either -network family. `selection` preserves the -API-provided `tokenID` when present and falls back to the token symbol. Sponsored +network family. `selection` preserves the quoted option index and the API-provided +`tokenID` when present, falling back to the token symbol. Sponsored transactions invoke the selector with an empty list; return `null` after acknowledging the free fee, or throw to stop execution. `FeeOptionSelector.firstAvailable` returns `null` for that empty list and continues execution as before. Unsponsored transactions @@ -622,7 +623,7 @@ val result = network = SolanaNetwork.Devnet, asset = "SOL", to = "solana-recipient-address", - amount = BigInteger("1000000"), + amount = java.math.BigInteger("1000000"), ) ``` @@ -675,24 +676,37 @@ val scopedIdToken = customClaims = mapOf("role" to JsonPrimitive("member")), ) -val credentials = omsWallet.wallet.listAccess(pageSize = 25u) +val grants = omsWallet.wallet.listAccess(pageSize = 25u) omsWallet.wallet.listAccessPages(pageSize = 25u).collect { page -> println(page.grants) } -credentials +grants .firstOrNull { !it.credential.isCaller } - ?.let { omsWallet.wallet.revokeAccess(credentialId = it.credential.credentialId) } + ?.let { grant -> + when (grant) { + is AccessGrant.Direct -> + omsWallet.wallet.revokeAccess(credentialId = grant.credential.credentialId) + is AccessGrant.Remote -> + omsWallet.wallet.revokeAccess( + credentialId = grant.credential.credentialId, + sessionId = grant.sessionId, + ) + } + } ``` For an owner-approved smart session, inspect the remote credential before showing consent, then authorize bounded EVM transfer grants. Backend credential registration and execution stay outside -this SDK surface. +this SDK surface. WaaS caps the requested session expiry at the remote credential's expiry. ```kotlin val credentialId = "remote-credential-id" val metadata = omsWallet.wallet.inspectRemoteCredential(credentialId) -showConsentScreen(metadata) +// Render these returned public fields in your app's consent UI before authorizing access. +println("${metadata.appName} ${metadata.appUrl}") + +val requestedExpiry = java.time.Instant.now().plusSeconds(3_600).toString() val session = omsWallet.wallet.authorizeRemoteAccess( @@ -702,15 +716,18 @@ val session = listOf( SmartSessionGrant.NativeTransfer( to = "0x1111111111111111111111111111111111111111", - limit = BigInteger("1000000000000000"), + limit = java.math.BigInteger("1000000000000000"), ), ), - expiresAt = "2099-01-01T00:00:00Z", + expiresAt = requestedExpiry, ) val details = omsWallet.wallet.getRemoteAccessSession(session.sessionId) val usage = omsWallet.wallet.getRemoteAccessSessionUsage(session.sessionId, Network.POLYGON) -omsWallet.wallet.revokeAccess(credentialId, session.sessionId) +omsWallet.wallet.revokeAccess( + credentialId = credentialId, + sessionId = session.sessionId, +) ``` ## API Reference diff --git a/docs/api.md b/docs/api.md index 990bd8c..66e9870 100644 --- a/docs/api.md +++ b/docs/api.md @@ -744,7 +744,10 @@ suspend fun inspectRemoteCredential(credentialId: String): RemoteCredentialMetad ### `WalletClient.authorizeRemoteAccess` -Authorizes owner-approved EVM smart-session grants for a remote credential. +Authorizes owner-approved EVM smart-session grants for a remote credential. Omit `sessionId` +to create a session; pass an existing session ID to replace that session's grants and +requested expiry without changing its signer. WaaS caps the effective expiry at the remote +credential's expiry. ```kotlin suspend fun authorizeRemoteAccess( @@ -823,7 +826,9 @@ suspend fun getIdToken( ### `WalletClient.revokeAccess` -Revokes a credential's access to the selected wallet. +Revokes one access grant from the selected wallet. Use `listAccess` or `listAccessPage` to +find the direct or remote grant. Omit `sessionId` for a direct grant; for a remote grant, +pass its session ID to revoke exactly that session. ```kotlin suspend fun revokeAccess( diff --git a/docs/error-contracts.md b/docs/error-contracts.md index 8d85c1f..bf5b212 100644 --- a/docs/error-contracts.md +++ b/docs/error-contracts.md @@ -50,7 +50,7 @@ whether `upstreamError` should be present, and which tests own the contract. | Protected wallet methods: `getIdToken`, signing and transaction methods, wallet import, `getTransactionStatus`, access authorization/usage/listing/revocation | Missing, expired, or stale local session | `OMSWalletSessionException` | Authenticate again or recover local session; no remote request was made | Absent | `PublicErrorContractsTest` | | Wallet auth, signing, transactions, import, and owner access methods | SDK-local validation or fee-selection failure | `OMSWalletValidationException` | Correct parameters or local fee selection; do not retry as an upstream outage | Absent | `PublicErrorContractsTest`, `WalletImportCryptoTest`, `WalletAccessTest` | | `client.wallet.getWalletImportRecipientKey`, `importWallet`, `importEncryptedWallet` | Recipient-key attestation is missing, stale, malformed, untrusted, or does not match the request/response | `OMSWalletAttestationException`, `OMS_ATTESTATION_VERIFICATION_FAILED` | Do not encrypt or submit key material; retry only after confirming the SDK's managed PCR0 trust policy and WaaS environment | Absent | `WalletImportCryptoTest` | -| `client.wallet.isValidMessageSignature`, `isValidTypedDataSignature` | WaaS validation backend failure | `OMSWalletRequestException` or `OMSWalletResponseException` with validation operation | Retry based on SDK code/status; log upstream detail | Present | `PublicErrorContractsTest` | +| `client.wallet.isValidMessageSignature`, `isValidSolanaMessageSignature`, `isValidTypedDataSignature` | WaaS validation backend failure | `OMSWalletRequestException` or `OMSWalletResponseException` with validation operation | Retry based on SDK code/status; log upstream detail | Present | `PublicErrorContractsTest` | | `client.wallet.sendTransaction`, `callContract` | Execute request fails after prepare | `OMSWalletTransactionException`, `OMS_TRANSACTION_EXECUTION_UNCONFIRMED`, `retryable = false`, `txnId` | Do not blindly resend the write; preserve `txnId` and upstream detail for diagnostics | Present when execute crossed transport/upstream boundary | `PublicErrorContractsTest` | | `client.wallet.sendTransaction`, `callContract` | Submitted transaction status polling fails | `OMSWalletTransactionException`, `OMS_TRANSACTION_STATUS_LOOKUP_FAILED`, `retryable = true`, `txnId` | Retry status lookup, not the original write | Present when polling crossed transport/upstream boundary | `PublicErrorContractsTest` | | `client.wallet.getTransactionStatus` | Direct status lookup backend failure | `OMSWalletRequestException` or `OMSWalletResponseException` with status operation | Retry status lookup or surface backend status to the user | Present | `PublicErrorContractsTest` | diff --git a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt index 4cadc8a..5294e04 100644 --- a/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt +++ b/oms-wallet-kotlin-sdk/src/main/java/technology/polygon/omswallet/wallet/WalletClient.kt @@ -1908,7 +1908,12 @@ class WalletClient private constructor( gateway.inspectRemoteCredential(projectId, credentialId) } - /** Authorizes owner-approved EVM smart-session grants for a remote credential. */ + /** + * Authorizes owner-approved EVM smart-session grants for a remote credential. Omit [sessionId] + * to create a session; pass an existing session ID to replace that session's grants and + * requested expiry without changing its signer. WaaS caps the effective expiry at the remote + * credential's expiry. + */ suspend fun authorizeRemoteAccess( credentialId: String, network: Network, @@ -2053,9 +2058,9 @@ class WalletClient private constructor( } /** - * Revokes a credential's access to the selected wallet. - * - * Use [listAccess] or [listAccessPage] to find credential IDs. + * Revokes one access grant from the selected wallet. Use [listAccess] or [listAccessPage] to + * find the direct or remote grant. Omit [sessionId] for a direct grant; for a remote grant, + * pass its session ID to revoke exactly that session. */ suspend fun revokeAccess( credentialId: String, diff --git a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt index b65afc5..8526a1a 100644 --- a/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt +++ b/oms-wallet-kotlin-sdk/src/test/java/technology/polygon/omswallet/PublicErrorContractsTest.kt @@ -692,6 +692,23 @@ class PublicErrorContractsTest { ), ), ), + labeled( + "wallet.isValidSolanaMessageSignature", + error( + name = "OMSWalletRequestException", + code = "OMS_REQUEST_FAILED", + operation = "wallet.isValidSolanaMessageSignature", + message = "WebRPC request failed", + retryable = true, + upstreamError = + upstream( + service = "Waas", + name = "WebrpcRequestFailed", + code = "-1", + message = "WebRPC request failed", + ), + ), + ), ), publicErrors( "wallet.isValidMessageSignature" to { @@ -711,6 +728,12 @@ class PublicErrorContractsTest { signature = "0xtyped", ) }, + "wallet.isValidSolanaMessageSignature" to { + client.wallet.isValidSolanaMessageSignature( + message = "hello", + signature = "solana-signature", + ) + }, ), ) } From 4ebe88b140b36ba3ae3fbda492267ea18e15ee00 Mon Sep 17 00:00:00 2001 From: tolgahan-arikan Date: Thu, 17 Sep 2026 16:16:34 +0300 Subject: [PATCH 10/10] chore(release): set version to 0.3.0 --- README.md | 2 +- gradle.properties | 2 +- oms-wallet-kotlin-sdk-waas-generated/build.gradle.kts | 2 +- oms-wallet-kotlin-sdk/build.gradle.kts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 771389e..3e3fed9 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ reads through a single `OMSWallet` root object. Maven Central: ```kotlin -implementation("io.github.0xsequence:oms-wallet-kotlin-sdk:0.2.0") +implementation("io.github.0xsequence:oms-wallet-kotlin-sdk:0.3.0") ``` ## Compatibility diff --git a/gradle.properties b/gradle.properties index 16d584e..2627070 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ kotlin.code.style=official # Publishing metadata POM_GROUP_ID=io.github.0xsequence POM_ARTIFACT_ID=oms-wallet-kotlin-sdk -POM_VERSION_NAME=0.2.0 +POM_VERSION_NAME=0.3.0 POM_NAME=OMS Wallet Kotlin SDK POM_DESCRIPTION=Android and Kotlin SDK for wallet, auth, and API integrations. POM_URL=https://github.com/0xsequence/kotlin-sdk diff --git a/oms-wallet-kotlin-sdk-waas-generated/build.gradle.kts b/oms-wallet-kotlin-sdk-waas-generated/build.gradle.kts index 3d605ab..dc8d960 100644 --- a/oms-wallet-kotlin-sdk-waas-generated/build.gradle.kts +++ b/oms-wallet-kotlin-sdk-waas-generated/build.gradle.kts @@ -16,7 +16,7 @@ ktlint { } group = providers.gradleProperty("POM_GROUP_ID").orElse("io.github.0xsequence").get() -version = providers.gradleProperty("POM_VERSION_NAME").orElse("0.2.0-SNAPSHOT").get() +version = providers.gradleProperty("POM_VERSION_NAME").orElse("0.3.0-SNAPSHOT").get() val waasGeneratedSource = layout.projectDirectory.file( diff --git a/oms-wallet-kotlin-sdk/build.gradle.kts b/oms-wallet-kotlin-sdk/build.gradle.kts index 2485f44..8f0477a 100644 --- a/oms-wallet-kotlin-sdk/build.gradle.kts +++ b/oms-wallet-kotlin-sdk/build.gradle.kts @@ -20,7 +20,7 @@ ktlint { } group = providers.gradleProperty("POM_GROUP_ID").orElse("io.github.0xsequence").get() -version = providers.gradleProperty("POM_VERSION_NAME").orElse("0.2.0-SNAPSHOT").get() +version = providers.gradleProperty("POM_VERSION_NAME").orElse("0.3.0-SNAPSHOT").get() evaluationDependsOn(":oms-wallet-kotlin-sdk-waas-generated") val waasGeneratedProject = project(":oms-wallet-kotlin-sdk-waas-generated")