Skip to content

Commit 8389518

Browse files
wip v28
1 parent 2df1e89 commit 8389518

10 files changed

Lines changed: 564 additions & 14 deletions

BITCODE_V28_QA.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,10 @@ Deposit path:
829829
13. In Network, capture the `/api/executions/history` request and response. Expected status is `201`.
830830
14. Run `v28_qa_terminal_02_activity_after_write` and confirm an `executions_recent` row with `type='agentic-execution:asset-pack'`, `context_summary.source='terminal-deposit-read-workbench'`, `context_summary.workbench='deposit'`, and a non-empty `output_summary.deposit`.
831831
15. Rerun `v28_qa_terminal_04_deposit_repository_alignment`; recent Deposit activity must reference the connected repository and must not reference `frontier/*`.
832+
16. Before `Submit deposit to Bitcode`, run `v28_qa_terminal_05_wallet_signer_gate`. For the current Leather staging path, `terminal_deposit_signer_gate_state` should be `wallet_signer_pending_signed_proof_accepted_for_v28_staging` or `wallet_signer_verified`.
833+
17. Click `Submit deposit to Bitcode`. The browser wallet should open a Bitcoin message-signing prompt for a `Bitcode deposit authorization` message before the API request is sent.
834+
18. Approve the signature. The request body must include a wallet authorization proof, and the UI should report a successful deposit submission rather than the staged-settlement error.
835+
19. If `/api/deposits` returns `Bitcode can draft transaction-bearing activity...`, rerun query 05 and paste the row with the Network response; this is a V28 signer-gate blocker.
832836
833837
Read path:
834838

packages/protocol/src/bitcode-demo.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,6 +2189,29 @@ function buildAddressingSurface(input, selectedInventoryEntries = [], extracted
21892189
};
21902190
}
21912191

2192+
/**
2193+
* @param {unknown} value
2194+
* @returns {null | Record<string, unknown>}
2195+
*/
2196+
function normalizeWalletAuthorizationProof(value) {
2197+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
2198+
const record = /** @type {Record<string, unknown>} */ (value);
2199+
const signature = typeof record.signature === 'string' && record.signature.trim() ? record.signature.trim() : null;
2200+
const message = typeof record.message === 'string' && record.message.trim() ? record.message.trim() : null;
2201+
if (!signature || !message) return null;
2202+
return {
2203+
kind: typeof record.kind === 'string' ? record.kind : 'bitcode_deposit_authorization',
2204+
proofKind: typeof record.proofKind === 'string' ? record.proofKind : 'bitcoin_message_signature',
2205+
provider: typeof record.provider === 'string' ? record.provider : null,
2206+
providerLabel: typeof record.providerLabel === 'string' ? record.providerLabel : null,
2207+
signerAddress: typeof record.signerAddress === 'string' ? record.signerAddress : null,
2208+
signedAt: typeof record.signedAt === 'string' ? record.signedAt : null,
2209+
messageHash: stableHashObject(message),
2210+
signatureHash: stableHashObject(signature),
2211+
signatureCaptured: true
2212+
};
2213+
}
2214+
21922215
/**
21932216
* @param {any} input
21942217
* @param {any} assetId
@@ -2199,6 +2222,7 @@ function buildAddressingSurface(input, selectedInventoryEntries = [], extracted
21992222
* @returns {any}
22002223
*/
22012224
function buildSigningSurface(input, assetId, contentRoot, addressingSurface, artifactSelectionSurface, githubAppAuthSurface) {
2225+
const walletAuthorizationProof = normalizeWalletAuthorizationProof(input.walletAuthorizationProof);
22022226
const signerAddress = input.signerAddress || input.authSession?.defaultSignerAddress || `did:key:${toSlug(input.author)}`;
22032227
const signingAlgorithm = input.signingAlgorithm || input.authSession?.signingAlgorithm || 'ed25519';
22042228
const keySource = input.keySource || input.authSession?.keySource || 'manual-upload-key';
@@ -2220,9 +2244,15 @@ function buildSigningSurface(input, assetId, contentRoot, addressingSurface, art
22202244
keySource,
22212245
statementKind: signedStatement.statementKind,
22222246
payloadHash: stableHashObject(signedStatement),
2223-
signatureChecksPass: input.signatureChecksPass !== false,
2247+
signatureChecksPass: input.signatureChecksPass !== false && (!walletAuthorizationProof || walletAuthorizationProof.signatureCaptured === true),
22242248
signedPayloadHashMatchesContentRoot: input.signedPayloadHashMatchesContentRoot !== false,
2225-
attestationHash: stableHashObject({ signerAddress, signingAlgorithm, payloadHash: stableHashObject(signedStatement) }),
2249+
attestationHash: stableHashObject({
2250+
signerAddress,
2251+
signingAlgorithm,
2252+
payloadHash: stableHashObject(signedStatement),
2253+
walletAuthorizationProof
2254+
}),
2255+
walletAuthorizationProof,
22262256
signedAddressingRoot: addressingSurface.addressingRoot,
22272257
signedSelectionRoot: artifactSelectionSurface.selectedInventoryRoot,
22282258
signedGitHubAppAuthRoot: githubAppAuthSurface.authPayloadHash
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
-- Saved query name: v28_qa_terminal_05_wallet_signer_gate
2+
-- Purpose: run before "Submit deposit to Bitcode" when the Terminal Deposit
3+
-- button reports wallet signing/settlement readiness. V28 staging accepts a
4+
-- provider-backed pending Bitcoin message signature as live signer readiness,
5+
-- while unsigned provider-session posture remains blocked.
6+
7+
WITH recent_wallet_users AS (
8+
SELECT
9+
u.id AS user_id,
10+
u.email,
11+
u.raw_app_meta_data ->> 'provider' AS auth_provider,
12+
u.raw_user_meta_data ->> 'sub' AS bitcoin_subject,
13+
u.created_at AS auth_created_at,
14+
u.last_sign_in_at
15+
FROM auth.users u
16+
WHERE
17+
u.raw_app_meta_data ->> 'provider' = 'custom:bitcode-bitcoin'
18+
OR u.raw_user_meta_data ->> 'sub' LIKE 'bitcoin:%'
19+
ORDER BY u.created_at DESC
20+
LIMIT 10
21+
),
22+
profile_wallets AS (
23+
SELECT
24+
p.id AS user_id,
25+
p.settings #>> '{bitcodeProfile,walletBinding,provider}' AS profile_wallet_provider,
26+
p.settings #>> '{bitcodeProfile,walletBinding,status}' AS profile_wallet_status,
27+
p.settings #>> '{bitcodeProfile,walletBinding,network}' AS profile_wallet_network,
28+
p.settings #>> '{bitcodeProfile,walletBinding,address}' AS profile_wallet_address,
29+
p.settings #>> '{bitcodeProfile,walletBinding,authAddress}' AS profile_auth_address,
30+
p.settings #>> '{bitcodeProfile,walletBinding,paymentAddress}' AS profile_payment_address,
31+
p.updated_at AS profile_updated_at
32+
FROM public.user_profiles p
33+
),
34+
wallet_connections AS (
35+
SELECT DISTINCT ON (c.user_id)
36+
c.user_id,
37+
c.provider,
38+
c.is_active,
39+
coalesce(c.connection_data ->> 'verification_state', c.connection_data ->> 'status') AS verification_state,
40+
coalesce(c.connection_data ->> 'proof_kind', c.connection_data ->> 'proofKind') AS proof_kind,
41+
coalesce(c.connection_data ->> 'address', c.connection_data ->> 'wallet_address') AS connection_address,
42+
c.connection_data ->> 'network' AS connection_network,
43+
coalesce(c.connection_data ->> 'authAddress', c.connection_data ->> 'auth_address') AS connection_auth_address,
44+
coalesce(c.connection_data ->> 'paymentAddress', c.connection_data ->> 'payment_address') AS connection_payment_address,
45+
coalesce(c.connection_data ->> 'addressType', c.connection_data ->> 'address_type') AS address_type,
46+
nullif(trim(coalesce(c.connection_data ->> 'message', '')), '') IS NOT NULL AS has_wallet_message,
47+
nullif(trim(coalesce(c.connection_data ->> 'signature', '')), '') IS NOT NULL AS has_wallet_signature,
48+
c.updated_at AS wallet_connection_updated_at
49+
FROM public.user_connections c
50+
WHERE c.provider IN ('bitcoin-wallet', 'unisat', 'leather', 'okx-bitcoin', 'xverse', 'manual-bitcoin')
51+
ORDER BY c.user_id, c.is_active DESC, c.updated_at DESC NULLS LAST
52+
)
53+
SELECT
54+
u.user_id,
55+
u.email,
56+
u.auth_provider,
57+
u.bitcoin_subject,
58+
w.provider AS wallet_provider,
59+
w.is_active AS wallet_active,
60+
w.verification_state,
61+
w.proof_kind,
62+
w.connection_network,
63+
w.connection_address,
64+
w.connection_auth_address,
65+
w.connection_payment_address,
66+
w.address_type,
67+
w.has_wallet_message,
68+
w.has_wallet_signature,
69+
p.profile_wallet_status,
70+
p.profile_wallet_network,
71+
p.profile_wallet_address,
72+
p.profile_auth_address,
73+
p.profile_payment_address,
74+
CASE
75+
WHEN w.user_id IS NULL THEN 'blocker:missing_wallet_connection'
76+
WHEN w.is_active IS DISTINCT FROM true THEN 'blocker:wallet_connection_inactive'
77+
WHEN w.verification_state = 'verified' THEN 'wallet_signer_verified'
78+
WHEN w.verification_state = 'pending'
79+
AND w.proof_kind = 'bitcoin_message_signature'
80+
AND w.has_wallet_message
81+
AND w.has_wallet_signature
82+
THEN 'wallet_signer_pending_signed_proof_accepted_for_v28_staging'
83+
WHEN w.verification_state = 'pending'
84+
THEN 'blocker:wallet_pending_without_signed_bitcoin_message_proof'
85+
ELSE 'blocker:wallet_connection_not_settlement_ready'
86+
END AS terminal_deposit_signer_gate_state,
87+
u.auth_created_at,
88+
u.last_sign_in_at,
89+
p.profile_updated_at,
90+
w.wallet_connection_updated_at
91+
FROM recent_wallet_users u
92+
LEFT JOIN profile_wallets p ON p.user_id = u.user_id
93+
LEFT JOIN wallet_connections w ON w.user_id = u.user_id
94+
ORDER BY u.auth_created_at DESC;

uapi/app/api/wallet/_shared.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ export type BitcodeWalletConnectionStatus = {
1616
connectionAddress?: string | null;
1717
matchesBindingAddress?: boolean;
1818
connectedAt?: string | null;
19+
network?: string | null;
20+
proofKind?: string | null;
21+
paymentAddress?: string | null;
22+
authAddress?: string | null;
23+
addressType?: string | null;
1924
mock_mode?: boolean;
2025
} | null;
2126
};
@@ -59,6 +64,23 @@ function readConnectionAddress(connectionData: UnknownRecord | null): string | n
5964
);
6065
}
6166

67+
function readConnectionProofKind(connectionData: UnknownRecord | null): string | null {
68+
return (
69+
normalizeString(connectionData?.proof_kind) ||
70+
normalizeString(connectionData?.proofKind) ||
71+
normalizeString(connectionData?.wallet_proof_kind) ||
72+
null
73+
);
74+
}
75+
76+
function hasSignedBitcoinWalletProof(connectionData: UnknownRecord | null): boolean {
77+
return Boolean(
78+
readConnectionProofKind(connectionData) === 'bitcoin_message_signature' &&
79+
normalizeString(connectionData?.message) &&
80+
normalizeString(connectionData?.signature),
81+
);
82+
}
83+
6284
function readConnectionVerificationState(
6385
connectionData: UnknownRecord | null,
6486
): BitcodeWalletConnectionVerificationState {
@@ -130,21 +152,34 @@ export async function readBitcodeWalletConnectionStatus(input: {
130152

131153
const connectionData = asRecord(data?.connection_data);
132154
const connectionAddress = readConnectionAddress(connectionData);
155+
const proofKind = readConnectionProofKind(connectionData);
133156
const liveVerificationState = readConnectionVerificationState(connectionData);
134157
const matchesBindingAddress =
135158
!connectionAddress || !walletBinding.address ? true : connectionAddress === walletBinding.address;
159+
const hasLiveSignedProviderProof =
160+
liveVerificationState === 'pending' && hasSignedBitcoinWalletProof(connectionData);
161+
const valid = Boolean(
162+
data &&
163+
matchesBindingAddress &&
164+
(liveVerificationState === 'verified' || hasLiveSignedProviderProof),
165+
);
136166

137167
return {
138168
connected: Boolean(data),
139169
provider,
140-
valid: Boolean(data && liveVerificationState === 'verified' && matchesBindingAddress),
170+
valid,
141171
address: walletBinding.address,
142172
verificationState: liveVerificationState ?? verificationState,
143173
metadata: {
144174
source: 'wallet_provider_connection',
145175
connectionAddress,
146176
matchesBindingAddress,
147177
connectedAt: normalizeString(data?.updated_at),
178+
network: normalizeString(connectionData?.network),
179+
proofKind,
180+
paymentAddress: normalizeString(connectionData?.payment_address) ?? normalizeString(connectionData?.paymentAddress),
181+
authAddress: normalizeString(connectionData?.auth_address) ?? normalizeString(connectionData?.authAddress),
182+
addressType: normalizeString(connectionData?.address_type) ?? normalizeString(connectionData?.addressType),
148183
},
149184
};
150185
}

0 commit comments

Comments
 (0)