Privy Signer
The Privy signer (github.com/smartcontractkit/crec-sdk/transact/signer/privy) signs CRE Connect operations through Privy's wallet-as-a-service API. It is designed for consumer applications where each end-user has their own embedded wallet.
When to use
- Per-user Smart Accounts whose signer is the user's Privy wallet.
- Server-side flows that need to act on behalf of a Privy-managed wallet (e.g. policy-gated background jobs).
- Existing apps already using Privy for auth and wallet provisioning.
For team-owned signers (production batch jobs, treasury operations) prefer AWS KMS, Vault, or Fireblocks.
Prerequisites
- Privy app ID and app secret.
- A Privy wallet ID for the user/account this signer drives.
- Network egress to
https://api.privy.io(or your configured base URL).
Construct the signer
Explicit parameters
import "github.com/smartcontractkit/crec-sdk/transact/signer/privy"
s, err := privy.NewSigner(
os.Getenv("PRIVY_APP_ID"),
os.Getenv("PRIVY_APP_SECRET"),
walletID,
)
if err != nil { return err }
walletID is the user-specific wallet identifier returned by Privy.
From environment
s, err := privy.NewSignerFromEnv()
Reads:
| Variable | Required | Notes |
|---|---|---|
PRIVY_APP_ID | yes | Your Privy app ID. |
PRIVY_APP_SECRET | yes | Privy app secret. |
PRIVY_WALLET_ID | yes | Wallet ID this signer signs for. |
PRIVY_BASE_URL | no | Defaults to https://api.privy.io. |
Inject a custom HTTP client (tests)
s, err := privy.NewSigner(appID, appSecret, walletID,
privy.WithHTTPClient(mockHTTP),
privy.WithBaseURL("https://api.privy.io"),
)
Read the wallet's address
addr, err := s.GetWalletAddress(ctx)
if err != nil { return err }
fmt.Println("Privy wallet address:", addr)
This is the address you add to AllowedEcdsaSigners when provisioning the corresponding CREC wallet; see Manage Wallet Signers.
Sign an operation
opHash, sig, err := client.Transact.SignOperation(ctx, op, s, chainSelector)
Internally Sign(ctx, hash):
- Hex-encodes the digest (
0x...). - POSTs
/v1/wallets/{walletID}/rpcwithmethod: "secp256k1_sign"and params{ "message": "0x...", "encoding": "hex" }. - Authenticates with Basic Auth (
appID:appSecret) plus theprivy-app-idheader. - Returns the raw signature bytes from the response.
The returned signature is suitable for ecrecover with the wallet's address.
Per-request flow
Each Sign call is one HTTP round-trip to Privy. There is no asynchronous approval flow in this signer: Privy handles authentication / authorisation policy internally based on your app config. If you want a confirmation UI, build it client-side before calling the server endpoint that triggers SignOperation.
Using server-side vs. client-side
The Privy signer in the CREC SDK uses the app secret and runs server-side. It is not a browser SDK. The typical architecture is:
- The user authenticates with Privy in the browser (Privy frontend SDK).
- The user requests an action from your service.
- Your service constructs the
types.Operation, callssigner.Signvia the Privy server-side signer, submits withTransact.SendSignedOperation.
If you need the user to physically click "Sign" before each operation, surface a confirmation in your UI before the server-side Sign is invoked, and persist the user's intent (signed message, JWT, etc.) so you can prove they consented.
Operational notes
- Throughput is bounded by Privy's API quotas; check your plan.
- Latency. Each
SignOperationmakes a single HTTP request to Privy's/v1/wallets/{walletID}/rpcendpoint with methodsecp256k1_sign. Total latency is set by Privy and the network path between your service and Privy. - Error model. Any non-200 from Privy surfaces as
RPC request failed with status <code>: <body>. Inspect the body to distinguish auth failures (401), policy rejections (403), or wallet-not-found (404).
Next steps
- Build and Sign Operations: construct the
Operationthat the Privy signer will sign. - Custom Signer: if Privy's flow doesn't fit, implement your own.