Repository navigation
feat!: add Tron support and replace walletAddress with activeWallet - #100
Draft
tolgahan-arikan wants to merge 36 commits into
Draft
tolgahan-arikan wants to merge 36 commits into
tolgahan-arikan wants to merge 36 commits into
Conversation
Port Tron wallet support from the TypeScript SDK (0xPolygon/oms-wallet-typescript-sdk#104). - Regenerate the WaaS client from 0xsequence/waas 8a2c7f6 (schema v1-26.9.22-f21ed9d2) with webrpc-gen v0.37.2 and gen-kotlin v0.3.2, adding PrepareTronTransaction, PrepareTronContractCall, the tron wallet type and network family, and networkFamily on IsValidTypedDataSignature. - Add WalletType.Tron, TronNetwork (tron:mainnet, tron:nile), sendTronTransaction, callTronContract, signTronMessage, signTronTypedData, isValidTronMessageSignature, isValidTronTypedDataSignature, and Tron private-key import. Tron transactions always use native mode, and sendTronTransaction forwards data exactly (omitted for a plain TRX transfer, "0x" for the payable fallback). - Add IndexerClient.getTronBalances through the OMS Tron indexer gateway, and use it to enrich Tron fee options so FeeOptionSelector.firstAvailable can pay unsponsored fees. - Check wallet families against the stored wallet type instead of the address shape, and reject bare-name violations in callContract and callTronContract before any request. - Validate Ethereum wallet addresses in wallet responses (hex shape only). BREAKING CHANGE: WalletClient.walletAddress is replaced by activeWallet (Wallet?). OMSWalletSessionState is renamed to OMSWalletSession; WalletClient.session is nullable with non-null expiresAt and auth, and is non-null exactly when activeWallet is. walletAddress is removed from WalletSelectionResult and CompleteAuthResult.WalletSelected (use wallet.address). OMSWalletSessionExpiredEvent gains a nullable wallet. The persisted session record now stores the full wallet (version 2); records saved by 0.3.x are discarded on load, so users sign in once after upgrading. callContract rejects full function signatures such as "transfer(address,uint256)"; pass the bare name.
Mirror the React example's TronExample: a wallet-type picker that uses or creates an Ethereum or Tron wallet, and a Tron panel on Nile with TRX and Nile USDT balances from getTronBalances, a faucet link, message signing with verification, and transfers of TRX (sendTronTransaction) or USDT/custom TRC-20 tokens (callTronContract with method "transfer"). Pending transfers with a hash are reported as submitted and link to Nile Tronscan. The EVM signature and transaction cards are shown only for Ethereum wallets.
Upgrade com.upokecenter:cbor from 4.4.4 to 4.5.6, which fixes CVE-2024-23684: crafted CBOR input could make decoding use excessive CPU. The SDK decodes CBOR only when verifying the wallet-import attestation documents returned by WaaS, so a malicious or compromised response could have stalled wallet import. Release note (ships with the Tron release): Security: upgraded the CBOR dependency used for wallet-import attestation verification to 4.5.6 to fix CVE-2024-23684 (excessive CPU when decoding crafted CBOR). No API changes. CBOR 4.5.6 also brings com.github.peteroupc:numbers 1.8.2 (from 1.8.0) and com.github.peteroupc:datautilities 1.1.0 as transitive runtime dependencies.
6 tasks done
- Signature verification: every isValid*Signature method takes an optional
walletAddress and no walletId. Requests always send networkFamily and
walletAddress (never walletId). An omitted address uses the active wallet's
address; no session throws OMSWalletSessionException and an active wallet of
another family throws OMSWalletValidationException before any request.
- callContract and callTronContract name the contract parameter contractAddress.
- CompleteAuthResult exposes credential and wallet (null while selection is pending).
- Rename WalletSelectionResult to WalletActivationResult, TokenBalancesResult to
BalancesResult, and OidcRedirectAuthMode to OidcAuthMode (wire values unchanged).
- TransactionStatusPollingOptions properties are timeoutMs, intervalMs,
fastIntervalMs, and fastPollCount (same defaults and order).
- sendTransaction(network, to, value) accepts mode (default Relayer).
- Indexer page requests and responses support column/before/after/sort cursors
(SortBy, SortOrder).
- Map WaaS AddressAlreadyImported (7313) to OMS_WALLET_ADDRESS_ALREADY_IMPORTED
(status 409, not retryable).
- OMSWalletUpstreamService gains wireValue ("waas", "indexer").
- OIDC parameter order matches TS/Swift for signInWithOidcIdToken and the custom
provider startOidcRedirectAuth.
- Docs: MIGRATION 0.4.0 entries, README, error contracts, regenerated API docs and
public API baseline; trails-actions session label now says OMSWalletSession.
The wallet-type picker now offers Solana. A Solana (Devnet) panel mirrors the Tron panel and the TypeScript React example: SOL and devnet USDC balances from getSolanaBalances (with loading and error states), SOL/USDC faucet and wallet explorer links, sign-and-verify via isValidSolanaMessageSignature with the wallet address, and relayed SOL, USDC, or custom SPL transfers with fee-option selection and a Solana Explorer link. Panel actions are disabled while one is in flight. The session card title now reads OMSWalletSession.
Every isValid*Signature method now throws OMSWalletValidationException
("walletAddress must not be empty") before any request when walletAddress
is provided but empty or blank. Omitting it still verifies against the
active wallet.
…ess from Java Callers use isEthereumHexAddress directly. The internal top-level helper is marked @JvmSynthetic so Java source cannot call it through WalletClientKt.
… property Code that creates or destructures the event positionally must account for it.
…xer errors The Tron panel now uses the same busy-guarded launcher as the Solana panel, generalised to launchWalletPanelAction, so a second tap cannot send a second transfer while one is in flight. A failed Nile balance lookup shows "Balances unavailable" instead of zero balances, and a new send clears the previous transaction hash and explorer button.
executePreparedTransaction takes one fee-option enricher instead of three mutually exclusive nullable networks, and the Solana and Tron enrichers share the token-address and FeeOptionWithBalance mapping. Requests and results are unchanged.
- Drop unused OMSWalletJson int/long/boolean/arrayOrEmpty helpers. - Drop the uncalled clearPendingOidcRedirectAuth and the always-true clearSession flags and OIDC callback clearPendingAuth flag. - Delete MonotonicNonceGenerator; OMSWalletTimestamps keeps nowMilliseconds. - Remove the never-read OidcRedirectAuthStore.synchronizationKey and its javap release check. - Drop the never-passed OMSWalletSessionStateMachine initialSnapshot. - WalletClient.create takes one TransactionStatusPollingOptions default. - Share writeTextAtomically, use APPLE_ISSUER, accessKeyHeaderName and OMSWalletHex.decode instead of local copies. No behaviour or public API change; the baseline only loses internal symbols.
gradle.properties defines every POM_* key, so the orElse fallbacks (including a stale 0.3.2-SNAPSHOT version) were never used.
Confirming a code calls completeEmailAuth once with the selected wallet selection behaviour, and the session lifetime falls back to DEFAULT_SESSION_LIFETIME_SECONDS instead of branching between overloads.
`javap -public` treats Kotlin `internal` declarations as public, so the baseline listed internal classes (for example OMSWalletSessionStateMachine), private classes' companions and serializers, name-mangled internal members and synthetic accessors. The dump now uses the binary-compatibility-validator library, which reads Kotlin metadata: 122 classes instead of 188, with no public API removed. The dump also fails if it contains mangled internal members or synthetic accessors, or if its top-level declarations differ from the source-based docs/api.md, so it cannot silently regress.
The validator keeps synthetic constructor bridges of non-public constructors, which name internal types (OMSWalletEnvironment, OMSWalletSessionStateMachine, ...). Nothing outside the SDK can call them, so they are dropped, and the dump now fails if any line references an SDK type that is not itself public (for example a leaked generated WaaS type).
The sample app's Tron and Solana panels labelled the txnId fallback as a hash, and trails-actions opened an explorer link built from txnId when no on-chain hash was returned yet. The explorer link now uses txnHash only.
- MIGRATION: drop the impossible 0.3.x Tron typed-data case and document that a blank walletAddress fails locally with OMSWalletValidationException. - README: use a placeholder Tron recipient instead of the mainnet USDT contract, scope the wallet-type rejection to the methods that check it, and list Solana/Tron in capabilities and the sample app. - AGENTS: point at the session storage files that exist.
The Solana panel switches between Devnet and mainnet and the Tron panel between Nile and mainnet. Each network has its own USDC mint or USDT contract, explorer links and faucet links (testnets only), and mainnet shows that transfers move real funds. Switching clears the previous results, and a balance response for a network the panel has left is ignored.
Pin the full PrepareSolanaTransfer, Solana and EVM SignMessage, AuthorizeRemoteAccess, InspectCredential, GetSession, GetSessionUsage, and Solana balance request bodies instead of substrings or values echoed from mocked responses. Spell the expected wallet-signature header out literally so the signer vectors no longer compare the production builder with itself.
Observe that ID-token sign-in clears the previous session before CommitVerifier, record the sponsored fee-selector input instead of asserting inside it, use distinct fast and slow poll intervals so the switch-over is visible, and pin ABI args in the contract-call vector.
Drop exact duplicates, generated-constant checks, and payload vectors already pinned by the signer vectors, after moving each test's unique assertions into its partner. Parameterize the wallet-activation failure, ignored OIDC callback, and sponsored fee-selector cases, fold the generated status-400 normalization into the central error contract test, and keep only local state checks in focused invalid-lifetime tests.
Move the duplicated enqueueJson, RecordedRequest body readers, and restored-wallet-client setup into WalletClientTestFixtures, and replace MockWebCryptoCredentialSigner with the equivalent TrackingCredentialSigner configured for nonce 42. No test assertions change.
The wallet-import attestation verifier rejected a real Nitro document captured from WaaS, so importWallet could not succeed against live WaaS: - it required CBOR tag 18, while Nitro sends an untagged COSE_Sign1 (a wrong tag is still rejected); - it built the certificate path from the root-first cabundle without reversing the intermediates; - it checked CA path-length constraints against that root-first order. The path now runs leaf -> intermediates -> pinned AWS Nitro root, matching the TypeScript and Swift SDKs. A positive test verifies the real document (shared with the Swift SDK's fixture), and it also checks that an untrusted PCR0 and a wrong COSE tag are still rejected.
Copy proto/clients/waas.gen.kt from waas origin/master (schema v1-26.10.19-5824b4d1), now emitted by webrpc v0.47.0's built-in kotlin target, keeping the SDK package name. Adds GetCredential, ClaimWallet, WalletStatus.Preallocated and SESSION_CAP_EXCEEDED. Align the hand-built Webrpc header with the new generator versions and document the refresh steps in AGENTS.md.
WaaS now slides a credential's expiry forward with use (waas #144, #152). The SDK reads the current expiry with GetCredential through one single-flight, revision-guarded primitive: in the background after restore and after signed calls once 6 hours have passed, and before treating a session as locally expired (expiry timer and protected operations). Servers without GetCredential (WebrpcBadRoute) keep exact local expiry; an unreachable WaaS keeps the session and the timer rechecks with backoff. A signed session request rejected with 7202 KeyNotFound or 7203 KeyExpired ends the session like local expiry and fails with OMS_SESSION_EXPIRED; unsigned WaasPublic calls are excluded. Refreshes never overlap other signed requests, so per-credential nonces stay ordered. Raise MAX_SESSION_LIFETIME_SECONDS to 180 days and document the current-expiry semantics.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds Tron wallet support to the Kotlin SDK, matching the TypeScript SDK (0xPolygon/oms-wallet-typescript-sdk#104): create, import and sign in with Tron wallets, sign and verify, send TRX and TRC-20 transfers, and read balances from the OMS Tron indexer. It also replaces
walletAddresswithactiveWalletand makessessionnullable. This is a breaking change; seeMIGRATION.md(0.4.0). A separate commit upgrades CBOR Java to 4.5.6 for CVE-2024-23684.Changes
Tron (
feat!commit)waasorigin/master@54d91262with webrpc-gen v0.47.0's built-inkotlintarget (which replaced gen-kotlin v0.3.2 upstream); apart from the package name (technology.polygon.omswallet.internal.generated.waas), it is identical to upstream'sproto/clients/waas.gen.kt. The hand-builtWebrpcheader now matches (webrpc@v0.47.0;kotlin@v0.47.0;waas@…).WalletType.Tron,TronNetwork(Mainnet,Nile) /TronNetworks. Tron import (WalletImportPrivateKey.Tron/TronBytes) uses the same secp256k1 key format as Ethereum.sendTronTransactionandcallTronContract, always native mode.datais forwarded exactly: omitted means nodatafield (plain TRX transfer),"0x"calls the payable fallback.signTronMessage,signTronTypedData,isValidTronMessageSignatureandisValidTronTypedDataSignature(networkFamily: tron).getTronBalancescalls/v1/TronIndexerGateway/GetTokenBalancesDetails, with the URL derived from the publishable key like the Solana gateway. Models mirror the Solana ones. When aselectFeeOptionselector is passed, the fee-option balance lookup uses it for unsponsored Tron fees.callContractandcallTronContractreject a full signature ("transfer(address,uint256)") locally. WaaS only accepts the bare name and builds the signature fromargs.Breaking changes (0.4.0)
WalletClient.walletAddressis replaced byactiveWallet: Wallet?.OMSWalletSessionStateis renamed toOMSWalletSession;sessionis nullable, with non-nullexpiresAtandauth. The internal class that already had that name is nowOMSWalletSessionStateMachine.walletAddressis removed from the wallet activation result (nowWalletActivationResult, see below) andCompleteAuthResult.WalletSelected. The session-expired event gainswallet.Sample apps (
feat(app)commit)app: an Ethereum/Tron picker in the Wallet card and a Tron panel (Nile by default, with a Nile ↔ Mainnet switch; the Solana panel has a Devnet ↔ Mainnet switch). It shows TRX and USDT balances with faucet links, signs and verifies a message, sends TRX / USDT / custom TRC-20 and shows "Transaction submitted.", and links to Tronscan.trails-actions: moved toactiveWallet. The explorer button now uses only the on-chaintxnHash.Security (
fix(deps)commit)com.upokecenter:cbor4.4.4 → 4.5.6 for CVE-2024-23684: crafted CBOR could make decoding use excessive CPU. The SDK decodes CBOR only when verifying wallet-import attestations. This also brings innumbers1.8.2 anddatautilities1.1.0 as transitive dependencies. The commit only touchesgradle/libs.versions.toml, and it ships in the same release as Tron.Differences from the TypeScript SDK
Walletmodel rather than a separate Tron wallet type.Webrpcheader usesgen-kotlin, like the Kotlin Solana header.Cross-SDK API alignment (
refactor!andfeat(app)commits)Aligns the public API with the TypeScript and Swift SDKs after a cited three-way API review. Breaking changes are made directly, with no deprecated aliases, and each one has a
MIGRATION.md0.4.0 entry.isValid…Signaturemethods take an optionalwalletAddressand no longer takewalletId. When the address is omitted, the active wallet's address is used. Requests never send a wallet ID.contractAddressreplacescontractoncallContractandcallTronContract.WalletActivationResult(wasWalletSelectionResult);BalancesResult(wasTokenBalancesResult);OidcAuthMode(wasOidcRedirectAuthMode; wire values unchanged);TransactionStatusPollingOptionsfieldstimeoutMs,intervalMsandfastIntervalMs(same defaults).modeonsendTransaction(network, to, value, …);CompleteAuthResult.walletandcredential;column/before/after/sort, withSortByandSortOrder;OMSWalletUpstreamService.wireValue.WalletAddressAlreadyImportedmaps WaaS error 7313 to status 409.signInWithOidcIdTokenand the custom-providerstartOidcRedirectAuthnow use the same order as Swift and TypeScript.includeMetadatadefault, the Tron balance wording, and the trails-actions session label.sortin indexer pages;TokenBalancesPageequality now includes the new fields.Review fixes and cleanup
After an outsider review and a simplicity review, every finding was re-checked against the code before changing it. The commits fall into these groups:
Fixes:
walletAddressin theisValid…Signaturemethods throwsOMSWalletValidationExceptionbefore any request, as in TypeScript and Swift;isEthereumAddressValuealias is removed, andisEthereumHexAddressis hidden from Java;walletis the new firstOMSWalletSessionExpiredEventproperty;Cleanup of code this PR added: Solana/Tron fee-balance enrichment is shared, and
executePreparedTransactiontakes one enricher instead of three nullable networks.Compat leftovers removed (0.4.0 is breaking):
balance,tokenID,metaTxnID,tokenIds,tokenId), dropping old alternate spellings the gateway never sends;balanceWei,tokenIDs,metaTxnId) that the gateway never sends.Cleanup of code already on master:
MonotonicNonceGenerator,synchronizationKey,initialSnapshotand a private OIDC helper removed;clearSessionflags removed;WalletClientpolling parameters are folded intoTransactionStatusPollingOptions;writeTextAtomically;OMSWalletHexis reused;IndexerClientduplications removed;0.3.2-SNAPSHOTone is gone);Sample apps: the Tron and Solana panels label the WaaS
txnIdas "Last transaction ID" when no on-chain hash exists yet, and trails-actions no longer opens an explorer link built fromtxnId.Docs pass:
walletAddressvalidation;AGENTS.mdfile reference is fixed.The public API is unchanged by these commits.
Public API baseline (
buildcommit)public-api.txtwas produced withjavap -public, which treats Kotlininternaldeclarations as public. As a result it listed:OMSWalletSessionStateMachine,PersistedSessionRecordandOMSWalletJson;This was already the case on master. The dump now uses the binary-compatibility-validator library (
0.18.1, withkotlin-metadata-jvm2.2.10), which reads Kotlin metadata. It is build-time only and nothing is added to the AAR. The baseline drops from 177 classes on master (188 on this branch before the change) to 122, and no public API is removed. Every top-level type in the new dump matchesdocs/api.md, and all 65 documented members are present (checked by hand; the build guard compares top-level names).The validator also keeps three synthetic constructor bridges of non-public constructors, which name internal types. Nothing outside the SDK can call them, so they are dropped.
To keep the baseline from regressing, the dump task fails in three cases:
docs/api.md.I confirmed the dump task catches a newly public class, a changed docs heading and a leaked generated type, and that the baseline check still catches a new public member.
Alternatives considered:
abiValidationisn't available for Android modules.When it is, the dump can switch to it.
Wallet-import attestation fix (
fixcommit)The attestation verifier rejected a real AWS Nitro document captured from WaaS, so
importWalletcould not succeed against live WaaS. This was already the case on master. There were three faults:cabundlewithout reversing the intermediates;The path now runs from the leaf through the reversed intermediates to the pinned AWS Nitro root, as in TypeScript and Swift. A wrong COSE tag is still rejected. A positive test verifies the real document, using the same fixture as the Swift SDK.
Test audit (
testcommits)An audit of every test: test-only commits, then a second mutation-based review pass. Every new or rewritten test was checked to fail when the behaviour it covers is broken. A test was removed or merged only when its partner already asserts everything it checked.
SignMessage(network);AuthorizeRemoteAccess(grants andcredentialId);networkFamily/keyOrigin; each case was checked to fail with its production check removed.scopein a real request body.TESTING.md/docs/error-contracts.mdnow name only tests that exist.Sliding session expiry (
chore(waas)andfeat(session)commits)WaaS now extends a login credential while it is in use (0xsequence/waas#144): the requested lifetime is a hard ceiling, and a 10-day idle window slides forward with use.
CompleteAuthreturns the current (sliding) expiry, and the newGetCredentialreads it back (0xsequence/waas#152). Without this change, a session longer than 10 days would be signed out locally on day 10 even while in use.GetCredentialafter restoring a session, when the local expiry is reached (before signing out), before an operation on a locally expired session, and in the background at most every 6 hours after successful wallet calls. The new value is saved and the expiry timer rescheduled. Concurrent callers share one request, and a result is applied only while the same session is current.GetCredential(WebrpcBadRoute, e.g. v1.1.1) keeps the previous behaviour, which is exact there.OMS_SESSION_EXPIRED.KeyNotFound/ 7203KeyExpiredon a request signed with the session credential end the session (expiry notification once) and fail the operation withOMS_SESSION_EXPIRED, with the WaaS error ascauseand noupstreamError, the same shape as a local expiry. A rejectedExecutereportsOMS_SESSION_EXPIRED(the middleware rejects it before execution); a status lookup after submission keeps its transaction error withtxnId. UnauthenticatedWaasPubliccalls are excluded (InspectCredentialreturns 7202 for an unknown remote credential), and 7207Unauthorizedis not mapped.15_552_000s); WaaS clamps rather than rejects, so this is safe on older servers.waasorigin/master@54d91262(webrpc-gen v0.47.0). BesidesGetCredentialit addsClaimWallet,claimablewallets,WalletStatus.Preallocatedand error 7208; the SDK does not use them yet and none are public.session-expiry-flow.md,docs/api.md) describeexpiresAtas the current expiry, extended by use.GetCredential, saved the server's expiry, and the next signed request waited for it). This SDK's refresh, gate and fallback are covered by unit tests with mutation proofs; not yet exercised live from the demo app.Release
POM_VERSION_NAME, the README dependency line and the generated module's snapshot fallback are set to 0.4.0 (chore(release): prepare 0.4.0).Testing
./gradlew ktlintCheckpasses./gradlew :oms-wallet-kotlin-sdk:testDebugUnitTestpasses (200 tests)./gradlew --build-cache verifypasses (ktlint, unit tests, Android lint, both apps, API baseline, API docs check)tools/check-expo-compatibility.sh minimumpassestools/check-expo-compatibility.sh currentpasses (Expo 58.0.13, after merging the fixture bumps frommaster)Wallet import on an emulator against dev: the CBOR 4.5.6 attestation path works end to end (a unit test also verifies a real Nitro document)
Instrumented tests (
connectedDebugAndroidTest) pass on a Pixel 9 emulator (API 36): 3/3Tested by hand on an Android emulator against the dev sandbox: Google sign-in,
importWalletwith an Ethereum private key (attestation verified, wallet listed as imported), a sponsored 1 TRX send on Nile and a 0.001 SOL send on Devnet (both submitted with a hash).Related