Skip to content

Commit

Permalink
docs: add externally managed wallet sections
Browse files Browse the repository at this point in the history
  • Loading branch information
sehyunc committed Dec 25, 2024
1 parent 817ad94 commit c764065
Showing 1 changed file with 248 additions and 0 deletions.
248 changes: 248 additions & 0 deletions docs.renegade.fi/docs/technical-reference/typescript-sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ The `Config` object is responsible for storing Renegade wallet state and interna
### Auth Config
The `AuthConfig` object is specifically used to interact with the relayer using API keys. If you are using a managed Renegade wallet, you will not need to interact with this object. If you are a permissioned user with API keys, you should follow the instructions [here](#createauthconfig) to create an `AuthConfig` object.

### External Config
The `ExternalConfig` object is used to interact with the relayer using an Externally Managed Wallet. The main difference between this and the `Config` object is that the `ExternalConfig` object does not contain a derivation key. Instead, the user will derive required fields of a wallet, such as the keychain, and custody the wallet secrets themselves. Learn more about Externally Managed Wallets [here](#external-key-management).

### Actions
This SDK exposes actions that are used to interact with a relayer, such as fetching a wallet's state or placing an order. Actions that access private wallet data require API authorization headers, which are automatically added for you when using the SDK. Browse the list of available actions [below](#actions-1).

Expand Down Expand Up @@ -248,6 +251,87 @@ export const config = createAuthConfig({
import { type AuthConfig } from '@renegade-fi/node'
```

### createExternalConfig

Creates a configuration object for interacting with the relayer using an externally managed wallet. The required secrets should be obtained from [generateWalletSecrets](#generating-wallet-secrets) first.

**Import**
```typescript
import { createExternalConfig } from "@renegade-fi/node"
```

**Parameters**
- `signMessage`: `(message: string) => Promise<string>`
- Function that signs messages using ECDSA secp256k1
- Must return signature as hex string with recovery bit (r[32] || s[32] || v[1])
- Must use raw message (no Ethereum message prefix)
- `publicKey`: `string`
- The public key corresponding to your signing function
- Used to verify signatures during API requests
- `symmetricKey`: `string`
- Generated symmetric key for API authentication
- Obtained from `generateWalletSecrets`
- `walletId`: `string`
- Unique identifier for your wallet
- Obtained from `generateWalletSecrets`
- `relayerUrl`: `string`
- HTTP URL of the relayer
- `websocketUrl`: `string`
- WebSocket URL of the relayer

**Basic Example**
```typescript
const config = createExternalConfig({
signMessage,
publicKey: "0x04800db50009a01fab58a239f204ca14e85682ca0991cb6914f34c4fbd0131eedb54d0ccbe392922e57486b031779bf8b6feab57971c2c406df291c0ab9c529a3d",
symmetricKey: walletSecrets.symmetric_key,
walletId: walletSecrets.wallet_id,
relayerUrl: "https://testnet.cluster0.renegade.fi:3000",
websocketUrl: "wss://testnet.cluster0.renegade.fi:4000",
});
```

**Signing Function Example**
```typescript
// Example signing function implementation
const signMessage = async (message: string) => {
// Validate input
if (!isHex(message)) {
throw new Error("Message must be a hex string");
}

// Hash the raw message (no Ethereum prefix)
const hashedMessage = keccak256(message);

try {
// Generate secp256k1 signature
const sig = await secp.signAsync(
hashedMessage.slice(2),
signingKey.slice(2),
{ lowS: true, extraEntropy: false }
);

// Format as r[32] || s[32] || v[1]
return concatHex([
numberToHex(sig.r, { size: 32 }),
numberToHex(sig.s, { size: 32 }),
numberToHex(sig.recovery ? 1 : 0, { size: 1 }),
]);
} catch (error) {
throw new Error(`Failed to sign message: ${error.message}`);
}
};
```

:::warning Important
The `signMessage` function and `publicKey` must form a valid signing pair. The relayer will verify that signatures produced by `signMessage` can be verified using `publicKey`.
:::

**Return Type**
```typescript
import { type ExternalConfig } from '@renegade-fi/node'
```

## Actions

### createWallet
Expand Down Expand Up @@ -1076,6 +1160,170 @@ An error may be thrown if:
- neither `baseAmount` nor `quoteAmount` is provided
- the API request authorization is incorrect / missing

## External Key Management

Renegade supports external key management as an alternative to the default managed wallet approach. This allows you to maintain complete control over your wallet's cryptographic secrets rather than having them derived and managed by the SDK.

### Why Use External Key Management?

External key management is ideal for:
- Users requiring complete control over key generation and storage
- Custom key management infrastructure integration
- Advanced security requirements where keys must be stored in specific hardware/systems
- Scenarios where key backup and recovery needs to be handled manually

### Key Components

The external key management flow consists of three main components:

1. **Wallet Secrets** - A set of cryptographic materials you generate and store:
- Wallet ID & symmetric key: Used for API authentication
- Blinder & share seeds: Used for wallet encryption
- Match key: Used during order matching

2. **ExternalConfig** - A configuration object that connects your externally managed keys to the SDK
- Does not store or derive keys internally
- Requires you to provide signing capabilities and public keys

3. **Actions** - Standard SDK actions that work with your external keys
- Require wallet secrets for initial wallet creation/lookup
- Support key rotation for ongoing security

### Generating Wallet Secrets

The `generateWalletSecrets` function creates the cryptographic materials needed for an externally managed wallet.

**Import**
```typescript
import { generateWalletSecrets } from "@renegade-fi/node"
```

**Parameters**
- `signer`: A function that generates a secp256k1 signature for a given message
- Input: Unhashed message as hex string
- Output: Signature as hex string with recovery bit
- Must NOT prefix with Ethereum signed message header

**Example**
```typescript
// Create a signer function that generates a secp256k1 signature for a given message
const signer = async (message: string) => {
// Hash the raw message (do not add Ethereum message prefix)
const hashedMessage = keccak256(message);

// Sign the hash with your private key
const sig = await secp.signAsync(
hashedMessage.slice(2),
env.PRIVATE_KEY.slice(2),
{ lowS: true, extraEntropy: false }
);

// Format signature as r[32] || s[32] || v[1]
return concatHex([
numberToHex(sig.r, { size: 32 }), // r component
numberToHex(sig.s, { size: 32 }), // s component
numberToHex(sig.recovery ? 1 : 0, { size: 1 }) // recovery bit
]);
};

const walletSecrets = await generateWalletSecrets(signer);
```

**Return Value**
```typescript
type GeneratedSecrets {
/** Identifies your wallet to the relayer */
wallet_id: string

/** Used to generate blinding values for wallet state encryption */
blinder_seed: `0x${string}`

/** Used to generate secret shares for wallet state encryption */
share_seed: `0x${string}`

/** Used to authenticate API requests to the relayer */
symmetric_key: `0x${string}`

/** Used during order matching process */
sk_match: `0x${string}`
}
```

:::warning Security Best Practices
- Store wallet secrets in secure, encrypted storage
- Never expose secrets in logs or client-side code
- Back up secrets securely - they cannot be recovered if lost
- Consider using a hardware security module (HSM) for production deployments
:::

:::info
Some Ethereum libraries (e.g. `viem`) automatically prefix messages with "\x19Ethereum Signed Message:\n". This will break signature verification - ensure your signer uses the raw message.
:::

### Wallet Creation / Lookup

Once you have generated wallet secrets, you can use them to create a wallet or lookup an existing wallet. The `createWallet` and `lookupWallet` actions require wallet secrets to be provided since they are externally managed. Each of these actions accepts an object containing the blinder seed, share seed, and match key as the second parameter.

```js
const walletSecrets = await generateWalletSecrets(signer);
const parameters: CreateWalletParameters = {
blinderSeed: walletSecrets.blinder_seed,
shareSeed: walletSecrets.share_seed,
skMatch: walletSecrets.sk_match,
};

// Create the wallet using the secrets
await createWallet(config, parameters);
```

:::note
Make sure you are using an `ExternalConfig` object when calling `createWallet` or `lookupWallet` with wallet secrets. Otherwise, the SDK will attempt to rederive the wallet secrets from wallet seeds and fail.
:::

### Actions

Once you have created an Externally Managed Wallet, you can use it to interact with the relayer. When using an Externally Managed Wallet, you should instantiate an [`ExternalConfig`](#createexternalconfig) object instead of a `Config` object and pass it as the first parameter actions. Otherwise, usage is the same as described in the [actions](#actions-1) section above.


### Key Rotation

Key rotation is a security best practice that involves updating your wallet's cryptographic keys. When using the default internally managed wallet, the SDK automatically rotates keys after each wallet update. For externally managed wallets, you'll need to handle key rotation manually.

#### Implementation

Keys can be rotated during any wallet update operation by providing a new public key. Your connected relayer will:
1. Verify the new public key is valid
2. Update the wallet's keychain
3. Use the new key for future operations

**Example**
```typescript
// Generate new signing keypair
const newSigningKey = generateNewSigningKey(); // Your key generation logic
const newPublicKey = derivePublicKey(newSigningKey);

// Execute operation with key rotation
const { taskId } = await deposit(config, {
// Key rotation parameter
newPublicKey: newPublicKey,

// Normal deposit parameters
fromAddr: account.address,
mint: token.address,
amount: depositAmount,
permitNonce: nonce,
permitDeadline: deadline,
permit: signature,
});

// Update config with new public key
config.setPublicKey(newPublicKey);
```

:::info
Key rotation is supported in all wallet update operations including deposits, withdrawals, and order placement.
:::

## Examples

### Create a wallet
Expand Down

0 comments on commit c764065

Please sign in to comment.