L1 Methods
L1 methods rely on EIP-712 signatures produced by your private key. Their sole purpose is initial setup — generating the L2 credentials (API key, secret, passphrase) you need for all trading operations going forward.
Client Initialization
Section titled “Client Initialization”L1 methods need a client with a signer attached, but no pre-existing API credentials.
use std::str::FromStr;use openfish_client_sdk::POLYGON;use openfish_client_sdk::auth::{LocalSigner, Signer};use openfish_client_sdk::clob::{Client, Config};
let private_key = std::env::var("OPENFISH_PRIVATE_KEY")?;let signer = LocalSigner::from_str(&private_key)? .with_chain_id(Some(POLYGON));
// The unauthenticated client can call L1 methods directlylet client = Client::new("https://api.openfish.fun", Config::default())?;Never commit private keys to version control. Always use environment variables or a secrets manager.
API Key Management
Section titled “API Key Management”create_api_key
Section titled “create_api_key”Generates a fresh API key (L2 credentials) for the wallet signer. Only one active API key per wallet — generating a new one invalidates the previous.
let creds = client.create_api_key(&signer, None).await?;println!("API Key: {}", creds.key());// Save creds.secret() and creds.passphrase() securely| Parameter | Type | Description |
|---|---|---|
signer | &impl Signer | Wallet signer for EIP-712 signature |
nonce | Option<u32> | Optional custom nonce for deterministic generation |
Response (Credentials):
| Field | Type | Description |
|---|---|---|
key | UUID | API key identifier |
secret | SecretString | HMAC signing secret (base64url-encoded) |
passphrase | SecretString | Authentication passphrase |
derive_api_key
Section titled “derive_api_key”Recovers an existing API key using a known nonce. If credentials were previously created with that nonce, the same credentials come back.
let creds = client.derive_api_key(&signer, Some(0)).await?;This is handy for restoring credentials on a new machine when you remember the nonce used at creation time.
create_or_derive_api_key
Section titled “create_or_derive_api_key”A convenience method that attempts to derive using the default nonce, falling back to creating a new key if none exists. This is the recommended entry point for initial setup.
let creds = client.create_or_derive_api_key(&signer, None).await?;This is the method authenticate() calls internally when no credentials are supplied.
Authentication Flow
Section titled “Authentication Flow”The simplest path combines L1 key derivation and client initialization in a single call:
let client = Client::new("https://api.openfish.fun", Config::default())? .authentication_builder(&signer) .authenticate() .await?;Under the hood, authenticate():
- Calls
create_or_derive_api_key()using L1 EIP-712 authentication - Stores the returned credentials in the client state
- Elevates the client from
UnauthenticatedtoAuthenticated<Normal> - If the
heartbeatsfeature is enabled, starts automatic heartbeat sending
Supplying Existing Credentials
Section titled “Supplying Existing Credentials”If you already have credentials (saved from a prior session, for instance), skip the derivation step:
use openfish_client_sdk::auth::Credentials;
let creds = Credentials::new( "your-api-key-uuid".parse()?, "your-secret".to_owned(), "your-passphrase".to_owned(),);
let client = Client::new("https://api.openfish.fun", Config::default())? .authentication_builder(&signer) .credentials(creds) .authenticate() .await?;You cannot supply both credentials and nonce — doing so returns a validation error.
EIP-712 Signature Details
Section titled “EIP-712 Signature Details”L1 headers are produced by signing a ClobAuth typed struct:
// The struct signed via EIP-712:struct ClobAuth { address: Address, timestamp: String, nonce: U256, message: String, // "This message attests that I control the given wallet"}
// Domain:Eip712Domain { name: "ClobAuthDomain", version: "1", chain_id: <your_chain_id>,}The resulting signature is sent in the OPENFISH_SIGNATURE header, alongside OPENFISH_ADDRESS, OPENFISH_TIMESTAMP, and OPENFISH_NONCE.
Signature Types
Section titled “Signature Types”When authenticating, you can specify how the funder address is determined:
| Type | When to Use | Funder |
|---|---|---|
| EOA (default) | Standalone wallet | Your wallet address directly |
| Proxy | Openfish proxy wallet | Auto-derived via CREATE2 |
| GnosisSafe | Openfish Safe wallet | Auto-derived via CREATE2 |
use openfish_client_sdk::clob::types::SignatureType;
let client = Client::new("https://api.openfish.fun", Config::default())? .authentication_builder(&signer) .signature_type(SignatureType::GnosisSafe) .authenticate() .await?;For Proxy and GnosisSafe types, the SDK automatically derives the funder address using CREATE2 address computation. You can also provide an explicit funder address with .funder(address).
Troubleshooting
Section titled “Troubleshooting”INVALID_SIGNATURE
Section titled “INVALID_SIGNATURE”Your private key is wrong or the chain ID does not match.
- Verify the key is a valid hex string (with or without
0xprefix) - Ensure chain ID is set:
.with_chain_id(Some(POLYGON)) - Only Polygon (137) and Amoy testnet (80002) are supported
NONCE_ALREADY_USED
Section titled “NONCE_ALREADY_USED”The nonce was already consumed to create an API key.
- Use
derive_api_key()with the same nonce to recover existing credentials - Or use a different nonce with
create_api_key()
Lost credentials but have nonce
Section titled “Lost credentials but have nonce”let recovered = client.derive_api_key(&signer, Some(original_nonce)).await?;Lost both credentials and nonce
Section titled “Lost both credentials and nonce”Create new credentials. The old ones are invalidated:
let new_creds = client.create_api_key(&signer, None).await?;See Also
Section titled “See Also”- Public Methods — Read market data without auth -> public
- L2 Methods — Place orders with API credentials -> l2
- Overview — Authentication architecture -> ../overview