feat(mobile): multisig proposal, approval, and execution flow - #552
Conversation
Adds /multisig, which turns the read-only multisig view into a working
coordination tool: an owner raises a transfer, owners approve it, and
the approval that reaches the threshold executes it.
There is no separate Execute action because the contract has no execute
entry point -- contracts/multisig-wallet performs the transfer inside
the sign_transaction invocation that reaches the threshold. The screen
names that approval for what it is ("Approve and execute") and warns
that it sends the funds immediately, rather than offering a button the
contract cannot back.
Three fixes carried over from the web port:
- propose_transaction takes caller as its first argument and calls
caller.require_auth(). The web version omits it, so every proposal it
builds is rejected. It is passed here.
- The owner is the source account rather than a separate fee payer, so
prepareTransaction resolves require_auth against the source account
and no second signature is needed. This also drops the fallback that
minted a throwaway keypair from Friendbot.
- Amounts convert through integer string arithmetic instead of
parseFloat(x) * 10_000_000, which misrounds ordinary values such as
0.7 at stroop precision.
Approval preconditions -- owner membership, duplicate approvals, and
whether the wallet can actually cover a transfer that is about to
execute -- are checked locally, so a rejection is immediate and
specific instead of arriving as a contract panic after a fee.
Deployment stays on the desktop wizard; the contract address is read
from the veil_multisig_contract key the web wallet already uses.
|
@Elizabethxxx is attempting to deploy a commit to the miracle656's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@Elizabethxxx Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
app/multisig.tsx replaces the route stub from Miracle656#507; README keeps both sections. Also repoint lib/multisig.ts at lib/network.ts. It captured EXPO_PUBLIC_NETWORK_PASSPHRASE and EXPO_PUBLIC_SOROBAN_RPC_URL into module constants at load time, so the runtime testnet/mainnet switch added in Miracle656#526 would not have reached it — proposals would keep going to whichever network the bundle started on. Resolved per call instead. tsc clean; jest 16 suites / 290 tests; expo lint clean.
|
Merging. This one actually submits — real Two things I want to note as done right, because I nearly "fixed" both before reading closely:
215 lines of tests over a 422-line module, suite now at 16 files / 290 tests. One change I made: const NETWORK_PASSPHRASE = process.env['EXPO_PUBLIC_NETWORK_PASSPHRASE']?.trim() || Networks.TESTNET;
const RPC_URL = process.env['EXPO_PUBLIC_SOROBAN_RPC_URL']?.trim() || ...#526 landed a runtime testnet/mainnet switch after you opened this, so those constants would have gone stale the moment a user switched — proposals would keep going to whichever network the bundle started on, silently. Now resolved per call through Verified: #484's acceptance — a proposal can be approved by required signers and executed — is structurally complete. Actually exercising it needs a deployed multisig contract and two signers, so it's worth a testnet run before relying on it. |
Turns the read-only multisig view into a working coordination tool: an owner raises a transfer, owners approve it, and the approval that reaches the threshold executes it.
closes #484
What is here
frontend/mobile/lib/multisig.ts— chain access plus the rules the screen applies before touching it.frontend/mobile/app/multisig.tsx— connect a wallet, view owners/threshold/balance, propose, approve, execute.Acceptance: a proposal can be approved by the required signers and executed
Propose → approve → approve (2-of-3) ends with the transfer made and the proposal showing Executed.
There is no separate Execute button, and that is deliberate.
contracts/multisig-wallethas noexecuteentry point:sign_transactionperforms the transfer inside the invocation that reaches the threshold. So the deciding approval is the execution. The screen names it accordingly — the button reads Approve and execute and warns that approving sends the funds immediately — rather than offering a button the contract cannot back.Three fixes carried over from the web port
The web wallet's
lib/multisig.tswas the reference. Three things did not survive review:propose_transactiontakescalleras its first argument and callscaller.require_auth(). The web version omits it, so every proposal it builds is rejected by the contract. It is passed here.parseFloat(x) * 10_000_000, which misrounds ordinary values at stroop precision (0.7lands a stroop short). This is money; there is a test pinning it.Failing fast instead of after a fee
Approval preconditions are checked locally, so a rejection is immediate and specific instead of arriving as a contract panic after a round trip and a fee: the signer is an owner, has not already approved, the proposal is not already executed, and — for the approval that will execute — the wallet actually holds enough XLM to cover the transfer. A non-deciding approval is still allowed when the balance is short, since funds can arrive before the transfer is attempted.
Scope and configuration
Deployment stays on the desktop wizard; this screen connects to an existing wallet. The contract address is stored under the same
veil_multisig_contractkey the web wallet uses. Network comes fromEXPO_PUBLIC_SOROBAN_RPC_URLandEXPO_PUBLIC_NETWORK_PASSPHRASE, defaulting to Soroban testnet.The signing key field is prefilled from this device's stored signer but stays editable, because approving on behalf of another owner means pasting a different key — the same affordance the web
PendingQueueoffers.The RPC wrappers are thin passes over the Stellar SDK and are exercised against a real contract rather than mocked; the tests cover the decisions made before submission.
Checks
npm run typecheckandnpm testpass infrontend/mobile.