This implementation creates a "one-time API wallet" using Nick's Method for keyless execution. The wallet can only execute ONE specific pre-authorized action, providing a secure way to delegate limited permissions without maintaining a private key.
Nick's Method enables "keyless execution" by:
- Creating a phantom signature: Using fixed signature components (v, r, s) - they don't need to be random
- Recovering the phantom address: Using
ecrecover(messageHash, v, r, s)to find the address that would "sign" that message - Single-use execution: Since no one controls the private key, the signature only works for ONE specific message
┌─────────────────────────────────────────────────────────┐
│ Step 1: Define the specific action to authorize │
│ (e.g., send 100 tokens to Alice) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 2: Hash the action data │
│ actionHash = keccak256(actionData) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 3: Use fixed signature values (v, r, s) │
│ v = 27 (pre-EIP155) │
│ r, s = fixed values (e.g., 1) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 4: Recover phantom wallet address │
│ phantomWallet = ecrecover(actionHash, v, r, s) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Step 5: Add phantom wallet as API wallet │
│ This gives it permission to execute core actions │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Result: Phantom wallet can ONLY execute the ONE │
│ action that matches actionHash with signature (v,r,s) │
└─────────────────────────────────────────────────────────┘
- Single-use: The wallet can only execute the ONE specific action that was hashed
- Keyless: No private key exists - the address is deterministically generated
- Non-replayable: After execution, the action cannot be replayed (system tracks executed actions)
- Trustless: No centralized key holder required
import {CoreWriterLib} from "./CoreWriterLib.sol";
contract MyContract {
using CoreWriterLib for *;
function createOneTimeSpotSend() external {
// 1. Define the specific action
address recipient = 0x1234...;
uint64 token = 0;
uint64 amount = 1000;
bytes memory actionData = abi.encodePacked(
uint8(1),
SPOT_SEND_ACTION,
abi.encode(recipient, token, amount)
);
// 2. Hash the action
bytes32 actionHash = CoreWriterLib.computeActionHash(actionData);
// 3. Create the one-time API wallet (uses fixed signature values internally)
address phantomWallet = CoreWriterLib.addOneTimeApiWallet(
actionHash,
"OneTime-SpotSend"
);
// Now phantomWallet can ONLY execute the specific spot send action
}
}The signature values (v, r, s) do not need to be random. The library uses fixed values internally:
v = 27(pre-EIP155)r = 0x0000000000000000000000000000000000000000000000000000000000000001s = 0x0000000000000000000000000000000000000000000000000000000000000001
These fixed values simply derive a deterministic phantom address via ecrecover. The security comes from the fact that:
- No one has the private key to the phantom address
- The signature only works for the specific action hash
- Each unique action hash produces a different phantom address
- Collision resistance: The probability of someone else controlling the phantom address is ~2^-160 (extremely low)
- Action-specific: The signature only validates for the exact action that was hashed
- Single execution: Once executed, the system marks it as processed (implementation-dependent)
- Action must be deterministic: The action data must be exactly as specified
- Pre-EIP155: Uses v=27 for cross-chain compatibility, no replay protection
- Requires coordination: The action data must be known in advance
| Feature | Regular API Wallet | One-Time API Wallet |
|---|---|---|
| Actions | Unlimited | Only ONE specific action |
| Private Key | Required | None (keyless) |
| Revocation | Can be revoked | Self-destructing after use |
| Use Case | Long-term delegation | Single authorized transaction |
- Pre-authorized trading: Allow a bot to execute ONE specific trade
- Scheduled transfers: Set up a future transfer without maintaining keys
- Conditional execution: Create an action that can only happen once
- Trustless delegation: Give temporary permission without key sharing
src/CoreWriterLib.sol- Main implementation (lines 178-216)src/examples/OneTimeApiWalletExample.sol- Usage examplesscripts/generate_one_time_wallet.py- Python helper script
function addOneTimeApiWallet(
bytes32 actionHash,
string memory name
) internal returns (address phantomWallet)Parameters:
actionHash: keccak256 hash of the specific action to authorizename: Human-readable name for the wallet
For advanced use cases requiring custom signature values:
function _addOneTimeApiWalletWithSignature(
bytes32 actionHash,
uint8 v,
bytes32 r,
bytes32 s,
string memory name
) internal returns (address phantomWallet)Returns:
phantomWallet: The deterministically generated address
- Nick's Method - Medium Article
- Original Nick Johnson Article
- Patronum Labs Nick Method NPM Package
- EIP-155: Simple Replay Attack Protection
MIT