Skip to content

Instantly share code, notes, and snippets.

@j-s
Last active April 6, 2026 17:02
Show Gist options
  • Select an option

  • Save j-s/db2b74226c0bbc585f5a79221eca7598 to your computer and use it in GitHub Desktop.

Select an option

Save j-s/db2b74226c0bbc585f5a79221eca7598 to your computer and use it in GitHub Desktop.

Privy Feedback — Building a Machine-to-Machine Payment Platform

Product: auto.exchange — AI agent marketplace where agents hire and pay each other via USDC on Tempo (chain 4217).

1. Server wallets can't sign Tempo type-0x76 transactions

The workaround: We have to manually serialize a type-0x76 tx, hash it, call secp256k1_sign on the raw hash via privy.wallets()._rpc() (an underscore-prefixed private API), parse the signature bytes manually (r/s/v extraction), re-serialize with the signature, and broadcast via eth_sendRawTransaction.

We verified with Privy's own documentation agent that eth_signTransaction exists but only supports standard Ethereum tx types (0, 1, 2, 4). Custom types like Tempo's 0x76 (with calls array and feeToken field) are rejected.

What we wanted: A chain-agnostic signRawTransaction that accepts arbitrary unsigned tx bytes and returns signed bytes — no tx type validation. This would work for Tempo, any L2 with custom tx types, and future chains. The existing secp256k1_sign on raw hashes works but requires ~70 lines of manual serialization, signature parsing, and broadcast logic.

Impact: ~70 lines of raw crypto plumbing that should be one line. Fragile — signature format assumptions, manual nonce management, hardcoded gas limits.

2. Three wallet types, only one works for payments

Privy creates embedded wallets (browser-only), smart wallets, and server wallets. Only server wallets work for our backend payment flow. But the frontend Privy SDK defaults to showing the embedded wallet. We had to:

  • Disable embedded wallet creation (createOnLogin: "off")
  • Never fall back to the Privy React wallet on the frontend
  • Always fetch server_wallet_address from our API
  • Document everywhere: "use server_wallet_address, not the Privy wallet"

What we wanted: One wallet type that works both client-side and server-side, or clear guidance on "for server-to-server payments, use X."

3. No way to get user's server wallet from JWT alone

Verifying a Privy JWT gives us user_id. To get the wallet, we have to call privy.users()._get(userId) (another underscore API), then search linked_accounts for the embedded wallet. The server wallet isn't in the JWT or in the user object — we provision it separately and store it in our own DB.

What we wanted: JWT claims to include the server wallet address, or verifyAccessToken to return it.

4. @privy-io/server-auth vs @privy-io/node confusion

We initially used @privy-io/server-auth (the documented package) before discovering it's deprecated in favor of @privy-io/node. The APIs are different. The node SDK uses privy.users()._get() instead of privy.getUser(), and privy.wallets() instead of separate wallet methods. This is documented nowhere obvious.

5. No native support for Tempo chain features

Tempo has: feeToken (pay gas in stablecoins), calls array (atomic multi-call), type-0x76/0x78 txs. None of these work through Privy's standard sendTransaction or eth_signTransaction. We ended up bypassing Privy's tx infrastructure entirely and using it purely as a key management service (secp256k1_sign).

What we wanted: Either support custom tx types in sendTransaction/eth_signTransaction, or provide a signRawBytes(walletId, unsignedTxBytes) method that's tx-type-agnostic. The latter is simpler and would work for any chain.


Summary: Privy works well as an auth + key management layer. The core ask is simple: a signRawBytes(walletId, bytes) method that signs arbitrary unsigned transaction bytes without validating tx type. This would eliminate 80% of our workarounds and work for any chain with custom tx types (Tempo, future L2s, etc.). The existing secp256k1_sign on raw hashes gets us there but with unnecessary complexity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment