Repository navigation
feat(flutter): SEP-10 wallet sign-in before a real hire (signChallenge with challenge checks, session on the Serverpod client) - #149
Merged
Conversation
Since #147 the server only accepts hire calls from a wallet-bound session. The app only read the Freighter address, so with HIRE=server every hire ended in "Sign-in with your wallet is not available yet". It also could not sign the challenge: FreighterWallet.signTransaction requires every source to be the connected account (#69), but a SEP-10 challenge's source is the server. Wallet: - WalletPort.signChallenge(SignInChallenge), with assertSep10Challenge: sequence 0 (never submittable), only manage_data operations, the first one "<home domain> auth" from the connected account, every other one from the server (the transaction source, never the account), time bounds that include now, and the server key and home domain when the app knows them. After signing, the body must be unchanged and carry a new signature from the connected account (shared with signTransaction). - MockWallet and WalletController implement it. Session: - WalletSession: createChallenge, wallet signature, verifyChallenge, then the AuthSuccess is stored by the client's FlutterAuthSessionManager, which sends it on every call. A session is reused only for the wallet that opened it; a challenge issued for another wallet is never signed. - main.dart sets client.authSessionManager and reads serverSigningKey and homeDomain from config.json auth when the server publishes them. Hire flow: - HireGateway.ensureSignedIn / forgetSession (no-op in the demo). The flow signs in after connecting and before createHire (new step signingIn). A session the server refuses (NotAuthenticated, AuthenticationUnavailable) or a refused sign-in (ChallengeExpired, ChallengeConsumed, ChallengeNotFound, InvalidWalletSignature: HireSignInFailed) is dropped, so Try again signs in afresh. ChallengeRateLimited maps to "too many sign-in attempts". Tests: SEP-10 challenges built like the server's codec, signed by a real key through the fake Freighter bridge (valid, and every refusal); session reuse, wallet switch, wrong-wallet challenge, declined and refused sign-ins; the sheet signs in before createHire and recovers after a refusal; readAuthConfig. 267 app tests pass, web release build succeeds, and breaking each of the 10 guards fails a test. Refs #136
Deploying puls3 with
|
| Latest commit: |
f07f76e
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://03337bbc.puls3-4lw.pages.dev |
| Branch Preview URL: | https://feat-136-app-wallet-sign-in.puls3-4lw.pages.dev |
2 of 4 tasks
Contributor
|
Note for testing this end to end: with a real funded testnet account, sign-in will fail on the server, independent of this PR. The signer lookup cannot decode a real account entry, so I reproduced it by signing a challenge directly against the server with a funded account, with no Flutter code involved. The fix is in #158 (server only). Once it is merged, this PR should work with a real Freighter account. |
3 of 6 tasks
…the agent's result (#150) - HireGateway.getHire returns HireProgress (from HireDetail): escrow status, run status, failure reason, result and the fund tx. HireStage derives Funded/Queued/Running/Result ready/Run failed/Submitted/ Completed/Rejected (Cancelled from open)/Expired; a succeeded run that is still `running` with a result reads as Result ready. - ServerHireGateway maps HireNotFound/HireNotOwned/InvalidHireId to HireNotFound and takes the payment link from the fund submission only. - /hires/:id (HireDetailScreen + HireDetailView): polls every 4 s until the hire is final, keeps the last status with a Retry banner when a refresh fails, asks to connect or sign in (SEP-10) when needed, copies the result, opens the payment on StellarExpert. - The hire sheet's confirmed state gets "View hire". - Pages build: PULS3_HIRE sets the HIRE dart-define (server or demo). - Approve and Reject are not offered: they need the server-signed submit (#97). Refs #28
moises-cisneros
self-requested a review
October 10, 2026 12:49
moises-cisneros
approved these changes
Oct 10, 2026
7 tasks
6 tasks done
TOMOKI977
added a commit
that referenced
this pull request
Oct 11, 2026
…ory and add a blueprint check (#167) Refs #23. - S04, S07 and S08 wireframes no longer matched the app: they predated the hire relay (#144), wallet sign-in (#149), the funds check (#159) and the Studio builder (#145). They are regenerated from the app at 1440 and 390 px; the other 30 generated wireframes still match and are unchanged. - screens.md: S04 states now cover the wallet sign-in step, the funds check before any signature, resuming an unfinished hire, and the confirming/held result. S06 lists HireDetailView. The inventory adds HireDetailView and Puls3Mark and explains the (new) planned components. - The wireframe tool fills a valid manifest before opening the deploy sheet, and seeds the demo wallet so the S08 PNG is the same on every run. - scripts/tests/blueprints.test.sh checks offline that every screen ID has a 1440 and a 390 px PNG, every wireframe is linked and every link resolves, every screen documents its four states, data, actions and components, and every Entity.field exists in docs/domain/model.md. Co-authored-by: Julio Cesar Severiche Orellana <39506930+TOMOKI977@users.noreply.github.com>
2 tasks
moises-cisneros
pushed a commit
that referenced
this pull request
Oct 14, 2026
…ore the wallet prompt (#136) (#176) Ports what the #174 draft added over main's sign-in (#149), onto main's WalletPort.signChallenge(SignInChallenge): - assertSep10Challenge now also requires a web_auth_domain operation from the server, checks its value when the app knows it, and verifies the server's own signature (the transaction source, on Testnet) before Freighter is prompted. Before, a challenge with no server signature was signed. - SignInChallenge, WalletSession and readAuthConfig carry webAuthDomain. readAuthConfig takes build-time pins (PULS3_AUTH_SERVER_SIGNING_KEY, PULS3_AUTH_HOME_DOMAIN, PULS3_AUTH_WEB_AUTH_DOMAIN) that win over config.json, because the static Pages build has no server-published auth block, so today it pins nothing. - cloudflare-pages-build.sh passes the pins as dart-defines, fails when PULS3_HIRE=server is missing any of them, and refuses a server key that is not a public G... address without echoing it. - README (Deploy) and .env.example list the three Pages variables and the values of the deployed server. The #174 config.json pinned GDVEU3..., puls3.app and auth.puls3.app; the live server signs with GBEEL... and uses puls3-hub-on-stellar.api.serverpod.space for both domains, so those pins would refuse every real challenge. They are not carried over. Tests: signed challenge with the pinned web_auth_domain; refused without web_auth_domain, with another web_auth_domain, unsigned, and signed by another key; pins over config.json; Pages build pins and fail-closed cases. Refs #136
9 tasks
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.
Refs #136
The app half of wallet-authenticated sessions. The server half is #147.
Summary
Since #147, the server accepts hire calls only from a wallet-bound session. The app connected Freighter and read the address, but never signed in, so with
HIRE=serverevery hire ended in "Sign-in with your wallet is not available yet". It also could not sign the challenge:FreighterWallet.signTransactionrequires every source to be the connected account (#69), and a SEP-10 challenge's source is the server.Wallet
WalletPort.signChallenge(SignInChallenge). The Freighter adapter validates the challenge withassertSep10Challengebefore any prompt:manage_dataoperations;<home domain> auth, from the connected account;config.jsonauth), the server signing key and the home domain.signTransactionuses the same check.MockWalletandWalletControllerimplement it.Session
WalletSessionrunscreateChallenge, then the wallet signature, thenverifyChallenge. TheAuthSuccessis stored by the client'sFlutterAuthSessionManager, which sends it on every call.main.dartsetsclient.authSessionManager. It readsserverSigningKeyandhomeDomainfromconfig.jsonauthwhen the server publishes them. The bundled config of the Pages build has noauth; the wallet checks still hold without it.Hire flow
HireGateway.ensureSignedIn/forgetSession. Both do nothing in the demo.createHire, in the new stepsigningIn("Sign in with your wallet: it only proves the account is yours").NotAuthenticated,AuthenticationUnavailable), or rejects the sign-in (ChallengeExpired,ChallengeConsumed,ChallengeNotFound,InvalidWalletSignature→HireSignInFailed), the session is dropped, so Try again signs in afresh.ChallengeRateLimited: "too many sign-in attempts".Acceptance criteria (#136, app half)
walletAuth.createChallenge/verifyChallenge, and the session is sent on every call.1.0.0+4df3ba4) returns 404 forwalletAuthandhireuntil it is redeployed frommain(chore: deploy server to Serverpod Cloud and Flutter web app #31).Verification evidence
New tests:
test/wallet/freighter_wallet_test.dart, groupsignChallenge:StellarSep10Codecand signed by a real key through the fake Freighter bridge;signTransactionrefuses it, which is why this path exists;manage_dataoperation, another account, another operation source, a wrong home domain, another server key, no or expired time bounds, the wrong network, a changed answer, no new signature.test/hire/wallet_session_test.dart:forget;test/screens/hire_sheet_test.dart:createHire;test/data/app_config_test.dart:readAuthConfig.I broke each guard one at a time to check that the tests catch it. All 10 changes made a test fail:
manage_data;Notes for reviewers
flutter_secure_storageas a direct dependency, which I left out.HIRE=demo. Flipping it and redeploying the server (chore: deploy server to Serverpod Cloud and Flutter web app #31) are what make the real flow live; see the demo analysis on feat: wallet-authenticated sessions (challenge sign-in) for server and app #136.