Repository navigation
feat!: add Tron support and replace walletAddress with activeWallet - #49
Draft
tolgahan-arikan wants to merge 30 commits into
Draft
tolgahan-arikan wants to merge 30 commits into
tolgahan-arikan wants to merge 30 commits into
Conversation
Port Tron support from the TypeScript SDK (0xPolygon/oms-wallet-typescript-sdk#104). - Regenerate the WaaS client from waas 8a2c7f6 (schema v1-26.9.22-f21ed9d2): adds PrepareTronTransaction, PrepareTronContractCall, the `tron` wallet type and network family, and `networkFamily` on IsValidTypedDataSignature. - Add WalletType.tron, TronNetwork/TronNetworks (tron:mainnet, tron:nile), sendTronTransaction (native mode, sun values, `data` forwarded as given), callTronContract, signTronMessage, signTronTypedData, isValidTronMessageSignature, isValidTronTypedDataSignature, and Tron private key import. - Add IndexerClient.getTronBalances through the OMS Tron indexer gateway and use it for Tron fee-option balances so FeeOptionSelector.firstAvailable works for unsponsored Tron fees. - Guard wallet-family methods by the stored wallet type instead of address shape. - Reject contract method names that are not bare function names before any request. - Validate Ethereum wallet addresses in wallet responses (shape only). BREAKING CHANGE: WalletClient.walletAddress is replaced by activeWallet (Wallet?). OMSWalletSessionState is renamed to OMSWalletSession; WalletClient.session is now optional with non-optional expiresAt and auth, and is non-nil exactly when activeWallet is. walletAddress is removed from WalletSelectionResult and CompleteAuthResult.walletSelected. OMSWalletSessionExpiredEvent gains `wallet`. Stored sessions now persist the full wallet (record version 2); version 1 records are discarded on load, so users sign in once after upgrading.
- Open SDK-sealed payloads with CryptoKit's HPKE.Recipient (macOS 14+/iOS 17+), including a tampered-ciphertext rejection. - Check the hand-written DHKEM(P-256, HKDF-SHA256)/HKDF-SHA256/AES-256-GCM base mode against the official CFRG test vector referenced by RFC 9180: enc, shared secret, key schedule context, secret, key, base nonce, and ciphertext. - Add an internal P256HPKE hook that takes the ephemeral key, info, and aad so the vector can be reproduced; the public seal path is unchanged.
5 of 7 tasks
… verification addresses - Sign-in no longer activates a wallet with an unknown network family or key origin that session restore would discard; it throws invalidResponse. - completeEmailAuth signs out when the auth response has an unreadable credential expiresAt, like the other post-auth failures. - isValid*Signature methods reject an empty or whitespace-only walletAddress with validationError before any request.
…nces The sdk-demo now picks the wallet type from the restored session and from OMSWalletSessionExpiredEvent.wallet, so signing in again after expiry returns to the same Tron, Solana, or Ethereum wallet. A Nile balance error now resets the TRX and USDT values like the Solana panel does.
Move decodeNonEmptyString to a KeyedDecodingContainer extension and use it for the Solana balance structs too, so an empty imageUrl or metadataUri decodes as nil for both families, as in the TypeScript and Kotlin SDKs.
requireActiveWalletType now runs the expiry/session check first and returns the active wallet, so verificationWalletAddress no longer calls requireActiveWalletId just for its side effect. An expired session with a wallet of another family now throws sessionExpired rather than validationError, matching the TypeScript SDK.
Every version 2 record is written with an expiry, so decode it as required and drop the redundant CodingKeys enum. A record without expiresAt is still discarded on load, as before.
Wallets map through networkFamily; WalletType.waasValue and WaasGenerated.WalletType.sdkValue had no callers.
The two gateway families now differ only in their indexer call; address deduplication and the FeeOptionWithBalance mapping are shared helpers. The EVM path uses the same dedupe and a plain [String: TokenBalance] map instead of a double optional.
walletId was stored separately but always set alongside activeWallet. It is now computed from activeWallet, as in the Kotlin SDK, and tests seed the active wallet instead of a bare wallet ID.
Drop clearAllPendingOIDCRedirectAuth, ByteUtils.hexToBytes, the always-nil contractTypes/contractBlacklist EVM filter fields, the unused jsonString(pretty:) parameter, the never-thrown TransactionError.pollingTimedOut and .missingTransactionHash cases, and the unreachable second noFeeOptionsAvailable guard. Wire requests are unchanged.
signOutOnFailure now takes the OIDC redirect ownership and skips the sign-out itself when a redirect callback owns the flow, replacing three copies of the ownership == nil branch.
The storage-error wrapping moves into the clearState closure, so the OIDC-store and plain paths no longer repeat it.
WalletCredentialSession had a line-for-line copy of the ISO-8601 parser and a makeCredentialSigner wrapper around signerFactory; both are gone. The restore-time expiry clock is unchanged.
Replace the hand-rolled HMAC extract/expand loop with HKDF<SHA256>, which is available on the package's macOS 12 / iOS 15 floor and produces the same output; the RFC 9180 vector tests still pass.
@testable import already exposes the 44 aliases from Sources/OMSWallet/Generated/WaasGeneratedAliases.swift; keep only the test-only ones.
The forbidden-symbol pattern listed nine request types that no longer exist and misspelled GetIDTokenRequest, while current request types were missing. Request type names are now read from the generated client; the one-item expected-error loop is a plain check.
- sdk-demo uses WalletClient.maxSessionLifetimeSeconds and Network.nativeTokenSymbol instead of local copies. - sdk-demo AppError drops a cancellation branch and an empty-details check that could not run. - trails-actions drops the unused formatSessionDate.
- MIGRATION: blank walletAddress validation, invalid credential expiresAt, ContractTokenBalance tokenID key - error-contracts: listWallets also rejects malformed Ethereum addresses - README: guard EVM indexer examples on Ethereum wallets; placeholder Tron recipient - useWallet/createWallet doc comments describe the persisted wallet; regenerate API.md - AGENTS/TESTING/publishing/error-contracts: API.md is generated by scripts/generate-api.sh
The Solana screens switch between Devnet and mainnet and the Tron screens 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 balances and transfer result, and a balance response for a network the screen has left is ignored.
A sign-in attempt that failed after a newer sign-in completed (for example an email-auth response with an unreadable credential expiresAt, or a later wallet-listing failure) signed out unconditionally and cleared the newer session, its signer key and its stored record. Failed attempts now clear the session only while it is still the one the attempt started from.
…onsored selector - Drive the delayed session-expiry task with a near-future expiry and a movable clock, so the timer itself has to fire (previously only the already-expired branch ran). - Assert ignored OIDC callbacks (unrelated redirect, invalid state) leave the pending flow unconsumed and that the real callback still completes sign-in. - Record sponsored EVM fee-selector invocations instead of asserting inside the closure.
Pin the OIDC ID-token parity vector to a literal token and handle hash computed outside the SDK, instead of recomputing the expected hash with a helper that mirrors the implementation.
Delete tests whose every assertion is held elsewhere, moving any unique assertion into the partner first: - signed-out nil state and custom-provider defaults move into the auth and redirect-start tests - the Tron signature test's isValid=false case becomes a true/false column of the parameterized verification matrix - tautological error-field, enum raw-value (pinned by API.md), and HPKE length tests go - session-lifetime validation becomes one test over four entry points and both bounds - fee-selector parsing, skip-polling, timed-out statuses, ID-token expiry, and key-import validation become parameterized tables - listAccess aggregation, indexer strictness rows, indexer HTTP error cases, and stored-wallet validation cases fold into their existing owners
Move exact-duplicate helpers into the shared fixtures in MockWalletTests.swift: one requestBodyData reader replaces three copies of the URL-protocol bodyData helper (and the dead MockIndexerURLProtocol copy), activateTestSession backs the three active-session helpers, testEnvironment replaces the copied default environment, and LockedBox moves out of the Tron tests. The URL protocols themselves differ in behaviour and stay separate.
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 Swift 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
walletAddresswith a typedactiveWalletand makessessionoptional. This is a breaking change; seeMIGRATION.md(0.4.0). A separate commit adds HPKE tests for wallet import.Changes
Tron (
feat!commit)waas.gen.swiftis copied unchanged from upstreamwaasorigin/master@8a2c7f6(proto/clients/waas.gen.swift, schemav1-26.9.22-f21ed9d2), generated withwebrpc-gen -schema=schema/waas.ridl -target=github.com/webrpc/gen-swift@v0.1.4 -client -visibility=internal -namespace=WaasGenerated -out=./clients/waas.gen.swift, the same command this repo uses.WalletType.tron,TronNetwork/TronNetworks(tron:mainnet,tron:nile). Tron import (.tron/.tronBytes) uses the same secp256k1 key format as Ethereum.sendTronTransactionandcallTronContract, always native mode.datais forwarded exactly:nilsends a plain TRX transfer,"0x"calls the payable fallback.signTronMessage,signTronTypedData,isValidTronMessageSignatureandisValidTronTypedDataSignature(networkFamily: tron).IndexerClient.getTronBalancescalls/v1/TronIndexerGateway/GetTokenBalancesDetails, with the URL derived from the publishable key like the Solana gateway. Models mirror the Solana ones. 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. The README's EVM example had the wrong format and is fixed..invalidResponse.Breaking changes (0.4.0)
WalletClient.walletAddressis replaced byactiveWallet: Wallet?.OMSWalletSessionStateis renamed toOMSWalletSession;sessionis optional, with non-optionalexpiresAtandauth.walletAddressis removed from the wallet activation result (nowWalletActivationResult, see below) andCompleteAuthResult(.walletSelected(wallet:wallets:credential:)). The session-expired event gainswallet.expiresAtcan't be parsed now fails with.invalidResponse, sinceexpiresAtis no longer optional.Docs and demos
README.md(Tron section),MIGRATION.md,docs/error-contracts.md, plus regeneratedAPI.mdand the public API baseline.HPKE tests (
testcommit, no release impact)HPKE.Recipientopens. A tampered ciphertext is rejected. Runs on macOS 14+ / iOS 17+.test-vectors.json). It checks enc, shared secret, key schedule, key, base nonce and ciphertext through a new internalP256HPKE.seal(...ephemeralPrivateKey:info:aad:)hook. The public seal path is unchanged.Differences from the TypeScript SDK
valueis aStringin sun, like other Swift amounts.Webrpcheader usesgen-swift, like the Swift Solana header.Cross-SDK API alignment (
refactor!andfeat(demo)commits)Aligns the public API with the TypeScript and Kotlin 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. An explicit address no longer requires a session, and requests never send a wallet ID.contractAddress:replacescontract:oncallContractandcallTronContract;callContract'sargsdefaults tonil.WalletActivationResultreplacesWalletSelectionResult.OMSWalletSession.expiresAtandOMSWalletSessionExpiredEvent.expiredAtare now ISO-8601Strings.walletId,Network.chainIdand thedecimals = 18default ofparseUnits/formatUnits;polygonAmoycase, which is nowamoy, along with its alias and the"polygonamoy"lookup..walletAddressAlreadyImportedmaps WaaS error 7313 to status 409.Oidccasing (wallet.startOidcRedirectAuth,wallet.handleOidcRedirectCallback), and upstream service values arewaas/indexer.TronBalanceandSolanaBalance;WalletImportPrivateKey.walletType;WalletClient.defaultSessionLifetimeSecondsandmaxSessionLifetimeSeconds.startOIDCRedirectAuthnow uses the same order as Kotlin and TypeScript.ecdsa-p256-sha256, and the Tron balance wording is corrected.AGENTS.mddescribes the new verification rule.findByName("polygonamoy")now returnsnil;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:
.invalidResponse; before, it was accepted and then silently signed out on every relaunch, deleting the signer key;expiresAtin email auth now gets the same sign-out cleanup as other post-auth failures;walletAddressin theisValid…Signaturemethods throws.validationErrorbefore any request, as in TypeScript and Kotlin;CompleteAuthResult.walletAddress;Cleanup of code this PR added:
imageUrl/metadataUritonil, like Tron, TypeScript and Kotlin (MIGRATION note);sessionExpired, as in TypeScript;expiresAt;walletIdis derived fromactiveWallet.Compat leftovers removed (0.4.0 is breaking):
balance,tokenID,metaTxnID,tokenIds,tokenId), dropping old alternate spellings the gateway never sends;TransactionTransfer,TokenMetadataandTokenMetadataAsset;Cleanup of code already on master:
completeWalletAuth,clearSessionandWalletCredentialSessionsimplified;waas.gen.swift, where it previously listed names that don't exist;Docs pass:
walletAddressvalidation, unparsable credential expiry and theContractTokenBalancekey;AGENTS.mdand the contributor docs sayAPI.mdis generated byscripts/generate-api.sh.The public API baseline is unchanged by these commits.
Stale sign-in fix (
fixcommit)A sign-in attempt that failed after a newer sign-in had completed signed out unconditionally, and cleared the newer session, its signer key and its stored record. Two paths did this: an unreadable credential
expiresAt, and a later wallet-listing or activation failure. Failed attempts now clear the session only while it is still the one they started from. Covered byTestStaleInvalidAuthResponseKeepsNewerSession.Test audit (
testcommits)An audit of every test: four test-only commits. 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.
trueandfalsesignature-verification results.Testing
swift buildpassesswift testpasses (145 tests)scripts/verify.shpasses (API generator tests, public API check,API.mdcheck,pod lib lint, both demo builds)Not tested by hand yet: the demo's Tron and Solana flows with a signed-in session (the TypeScript example's Tron flows were tested against dev on Nile).
Related