Snapshot of the shared working tree's outstanding changes (multiple concurrent workstreams), excluding generated runtime state files (.football-tote-state.json, .tote-platform-state.json, .usdm-historical-chart.html). Spans: wallet shell (connect/, keystore MultiKeyStore+vault, screens, currency picker, sign-message), verify artifacts, new Potidaea/DRA/tote contracts + regtest suites, cross-chain/T6 + WalletConnect Parameters, a telegram-orderbook subproject, design mockups, and docs. See shells/app/WALLET-COORDINATION.md for the per-file ownership split. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
110 lines
5.2 KiB
TypeScript
110 lines
5.2 KiB
TypeScript
/**
|
|
* src/connect/proposer.ts — the generic dApp-side ("proposer") connector core,
|
|
* shared by every Deviant dApp (tote, checkers, event-prediction) and by both
|
|
* wallet-connection protocols (WalletConnect wc2-bch-bcr + CashConnect).
|
|
*
|
|
* Roles (see spike/wc-connect/README.md):
|
|
* - dApp = PROPOSER: opens a session, builds an unsigned tx leaving the USER's
|
|
* own inputs as placeholders, and requests the wallet co-sign them.
|
|
* - wallet = RESPONDER: connects, and signs only its own inputs.
|
|
*
|
|
* This module is protocol-agnostic: a dApp builds its transaction with CashScript
|
|
* (covenant inputs carry their real unlockers; each user input is added with
|
|
* `placeholderP2PKHUnlocker(userAddress)`), turns it into a `WcTransactionObject`
|
|
* via `buildCoSignRequest`, and hands it to a `WalletSession` (a WalletConnect or
|
|
* CashConnect adapter). Validated handshake: snapshot 0055.
|
|
*
|
|
* What is shared across protocols is the TX-BUILDING (the `TransactionBuilder` with
|
|
* placeholder user inputs). The WIRE REQUEST is NOT identical across protocols:
|
|
* - WalletConnect (wc2-bch-bcr): `bch_signTransaction` takes the `WcTransactionObject`
|
|
* verbatim, serialized via `toWcParams` (libauth `stringify`). This is the
|
|
* device-validated path (snapshot 0057).
|
|
* - CashConnect (CashRPC, v0.0.25): `bch_signTransaction_V0` is **wallet-
|
|
* orchestrated**, not a dApp-built pass-through. Template-scoped inputs use the
|
|
* BchNamespace `template` (a libauth `WalletTemplate` the dApp CAN supply at
|
|
* session establishment), but the wallet then auto-runs
|
|
* `usingUnspents → addTokensFromUnspents → addTokenChangeOutputs →
|
|
* addSatoshisFromUnspents → addSatoshiChangeOutput` and VM-`verify`s before
|
|
* signing. Net: CashConnect V0 is the right fit for **standard wallet-side flows**
|
|
* (plain BCH sends, CashTokens FT/NFT sends) — the wallet builds and signs. For
|
|
* Deviant's **dApp-built covenant txs** (tote `bet()`, `GamePoolEscrowFlat.fund()`),
|
|
* route through WalletConnect: the dApp-supplied-template path is technically
|
|
* open but blocked in practice by the auto-orchestration breaking covenant
|
|
* structure, wallets' refusal to run arbitrary dApp templates, and the VM-verify
|
|
* gate. `toWcParams` below remains WalletConnect-only.
|
|
* Multi-protocol wallets (e.g. Cashonize) speak both, so users never see the routing
|
|
* decision. See spike/wc-connect/cashconnect.mjs + TESTING.md §2 for the CashConnect
|
|
* handshake test.
|
|
*/
|
|
import { TransactionBuilder } from 'cashscript';
|
|
import {
|
|
placeholderP2PKHUnlocker,
|
|
type WcTransactionObject,
|
|
type WcTransactionOptions,
|
|
} from 'cashscript';
|
|
import { stringify } from '@bitauth/libauth';
|
|
|
|
export { placeholderP2PKHUnlocker };
|
|
export type { WcTransactionObject, WcTransactionOptions };
|
|
|
|
/** What the wallet returns from `bch_signTransaction`. */
|
|
export interface SignedTxResult {
|
|
/** Fully-signed transaction hex. */
|
|
readonly signedTransaction: string;
|
|
/** Its txid (hash). */
|
|
readonly signedTransactionHash: string;
|
|
}
|
|
|
|
/**
|
|
* A connected wallet session — the seam every adapter implements, so a Deviant
|
|
* dApp is written ONCE against this interface and works over WalletConnect,
|
|
* CashConnect, or (later) WizardConnect. Concrete adapters wrap the relay/SDK
|
|
* (device-side; see spike/wc-connect/{connect,cashconnect}.mjs).
|
|
*/
|
|
export interface WalletSession {
|
|
/** The bch account(s) the wallet exposed at pairing (cashaddr). */
|
|
getAddresses(): Promise<string[]>;
|
|
/** Ask the wallet to co-sign the placeholder inputs of a proposed tx. */
|
|
signTransaction(request: WcTransactionObject): Promise<SignedTxResult>;
|
|
}
|
|
|
|
/**
|
|
* Turn a fully-built (but user-inputs-unsigned) transaction into a co-sign
|
|
* request. Thin wrapper over CashScript's `generateWcTransactionObject` so every
|
|
* dApp has one named entry point (and a place to normalise across protocols if
|
|
* they ever diverge). The builder must already have:
|
|
* - covenant inputs added with their real `contract.unlock.*(...)` unlockers,
|
|
* - each user input added with `placeholderP2PKHUnlocker(userAddress)`,
|
|
* - all outputs added.
|
|
*/
|
|
export function buildCoSignRequest(
|
|
tb: TransactionBuilder,
|
|
opts: WcTransactionOptions = {},
|
|
): WcTransactionObject {
|
|
return tb.generateWcTransactionObject(opts);
|
|
}
|
|
|
|
/**
|
|
* Serialize a `WcTransactionObject` into the JSON-RPC `params` a BCH wallet
|
|
* expects for `bch_signTransaction` (wc2-bch-bcr). The wire format is **libauth's
|
|
* `stringify`**: `Uint8Array → "<Uint8Array: 0x..>"`, `bigint → "<bigint: ..n>"`.
|
|
* Wallets (e.g. Cashonize) validate these exact patterns and reject plain
|
|
* hex/decimal (or raw arrays/numbers) — so an adapter MUST pass `toWcParams(req)`,
|
|
* never the raw object, to `client.request`.
|
|
*/
|
|
export function toWcParams(req: WcTransactionObject): unknown {
|
|
return JSON.parse(stringify(req));
|
|
}
|
|
|
|
/**
|
|
* Build the co-sign request and send it to the wallet in one step. The dApp signs
|
|
* its covenant inputs; the wallet fills the placeholder (user) inputs and — when
|
|
* `broadcast` is set — broadcasts. Returns the signed tx.
|
|
*/
|
|
export async function proposeCoSign(
|
|
session: WalletSession,
|
|
tb: TransactionBuilder,
|
|
opts: WcTransactionOptions = {},
|
|
): Promise<SignedTxResult> {
|
|
return session.signTransaction(buildCoSignRequest(tb, opts));
|
|
}
|