feat(auth): Privy embedded wallet → Cosmos signer adapter#23
Merged
Conversation
Privy provides embedded EVM/Solana wallets but no native Cosmos signer. This adapter bridges a Privy-held key to a cosmjs OfflineDirectSigner so consumers can use Privy for auth/onboarding while still calling every Sentinel SDK helper that takes a wallet (connect, broadcast, fee-grants, operator helpers). Two strategies, picked by mode: * mode='mnemonic' — consumer triggers Privy exportWallet() once, hands the seed to the adapter, which derives a Cosmos secp256k1 key on the standard m/44'/118'/0'/0/0 path. Trust model is identical to a normal mnemonic wallet. * mode='rawSign' — seed never leaves Privy. Consumer supplies the compressed pubkey plus a signRawSecp256k1(digest32) callback. The adapter computes sha256(makeSignBytes(signDoc)) locally and ships only the digest. Returned (r||s) signature is normalized to low-S (cosmos-sdk rejects high-S since v0.42). Both modes derive the SAME sent1 address from the same seed. deriveCosmosPubkeyFromMnemonic is exposed for pre-computing the address during Privy onboarding before the signer is wired up. Tests: 20 assertions covering address parity across modes, SignDoc digest derivation, signature verification against the pubkey, low-S normalization, signerAddress-mismatch rejection, factory routing, and static facade delegation. No network or Privy SDK required — the raw-sign callback is simulated locally with cosmjs Secp256k1.
4 tasks
Sentinel-Bluebuilder
added a commit
that referenced
this pull request
Apr 27, 2026
…adcast) (#24) The PrivyCosmosSigner adapter shipped in #23 produced a cosmjs OfflineDirectSigner but consumers had to hand-build a SigningStargateClient to use it. This wires it into SentinelClient directly: new SentinelClient({ signer: privySigner, rpcUrl }) await client.getBalance() // works — uses signer's address await client.getClient() // works — passes signer to SigningStargateClient getWallet() now resolves in this order: 1. per-call mnemonic (override) 2. constructor-supplied signer (Privy raw-sign, Keplr, etc.) 3. constructor-supplied mnemonic (the original path) Tunnel handshake constraint: connect() / autoConnect() / connectPlan() throw a helpful "VPN connect/disconnect requires a mnemonic" error when only a signer is supplied, because the WireGuard/V2Ray handshake signs locally with the raw secp256k1 privkey before any chain TX. Privy's raw-sign endpoint cannot reach into that primitive without a handshake refactor (deferred — out of scope here). The error message points to docs/PRIVY-INTEGRATION.md for the full table. Tests: test/privy-client-integration.test.mjs — 12 assertions covering signer-mode getWallet shape, address parity with mnemonic mode, helpful error for missing auth, and rejection of all three connect entry points in signer-only mode. Existing privy-cosmos-signer.test.mjs (20) still passes; smoke test 670/671 passing (the one pre-existing SDK_VERSION mismatch is unrelated). Docs: docs/PRIVY-INTEGRATION.md gains a "Tunnel connect/disconnect — Mode A only" section with the operation-by-mode table, plus a SentinelClient usage example. Co-authored-by: Human and Agent dVPN <[email protected]>
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
Privy provides embedded EVM/Solana wallets but has no native Cosmos signer. This PR adds an adapter that bridges a Privy-held key to a cosmjs
OfflineDirectSigner, so consumers can use Privy for auth/onboarding while still calling every Sentinel SDK helper that takes awallet(connect,broadcast,broadcastWithFeeGrant, operator helpers, etc.).Two strategies
Picked by the
modefield oncreatePrivyCosmosSigner.Mode A —
mnemonic(seed-import)Consumer triggers Privy's
exportWallet()once. The adapter re-derives a Cosmos secp256k1 key on the standardm/44'/118'/0'/0/0path and wraps it inDirectSecp256k1HdWallet. Trust model is identical to a normal mnemonic wallet.Mode B —
rawSign(custody-preserving)Seed never leaves Privy. Consumer supplies the compressed pubkey + a
signRawSecp256k1(digest32)callback. The adapter computessha256(makeSignBytes(signDoc))locally and ships only the 32-byte digest. Privy's raw secp256k1 sign endpoint returns(r||s); the adapter normalizes to low-S (cosmos-sdk rejects high-S since v0.42).Both modes derive the same
sent1...address from the same seed.What's exported
PrivyCosmosSigner(static facade withfromMnemonic,fromRawSign,create,derivePubkeyFromMnemonic)PrivyRawSignDirectSigner(the OfflineDirectSigner class)privyCosmosSignerFromMnemonic,privyCosmosSignerFromRawSign,createPrivyCosmosSignerderiveCosmosPubkeyFromMnemonic— precompute thesent1...address during Privy onboarding before the signer is wired upAll wired through
index.js.Tests
test/privy-cosmos-signer.test.mjs— 20 assertions covering:createWallet()deriveCosmosPubkeyFromMnemonicmatches Mode AsignDirectproduces a signature that verifies against the pubkey onsha256(makeSignBytes(signDoc))signerAddressmismatch is rejected with a helpful messageNo network, no Privy SDK in the test — the raw-sign callback is simulated locally with cosmjs
Secp256k1.createSignature. The contract this PR establishes is what a real Privy raw-sign endpoint must satisfy.Docs
docs/PRIVY-INTEGRATION.md— usage for both modes, address parity guarantees, requirements on the Privy callback (raw 32-byte digest, noeth_sign-style prefixing, must use Cosmos coinType 118), failure-mode table.Test plan
node test/privy-cosmos-signer.test.mjs— 20/20 passnode -e "import('./index.js').then(...)"— 401 exportsnode test/smoke.js— no new failures (only the pre-existingSDK_VERSION is 1.0.0failure unrelated to this change)