A minimalistic server-based allocator for The Compact. Smallocator provides an API for sponsors to request resource lock allocations across multiple blockchains, with support for EIP-4361 session authentication and signing EIP-712 Compact
messages. It also includes a frontend application for interacting directly with the server that also facilitates making deposits into resource locks it oversees.
β οΈ Smallocator is under developement and is intended to serve as a reference for understanding server-based allocator functionality and for testing purposes. Use caution when using Smallocator in a production environment.
- π Secure session-based authentication for sponsors using EIP-4361
- βοΈ EIP-712 Compact message validation and signing on demand from session-gated sponsors
- π€« No witness data or signature provided, keeping sponsor intents secret (only the typestring and witness hash is supplied)
- π GraphQL integration with The Compact Indexer for multi-chain indexing
- πΎ Persistent storage using PGLite to track attested compacts and used nonces
- π Comprehensive validation pipeline to ensure resource locks never end up in an overallocated state
- βοΈ Single-resource-lock, single-chain compacts only: No
BatchCompact
orMultichainCompact
attestations - βοΈ Strict nonce usage: Ensures every attested nonce is unique; no reuse on expirations and no direct onchain nonce consumption
- π§ No
attest()
callbacks for ERC6909 transfers: focused solely on attesting compacts - πͺ No compact qualification: Attests to the exact compact provided to it without adding qualifiers or extra metadata
- π‘ No direct on-chain awareness: Relies entirely on indexer and internal attestation state
- β³ Straightforward finalization: Uses a simple, time-based approach per chain for determining transaction finality
A lightweight hosted version is currently deployed to https://smallocator.xyz. Note that it's not ready for any kind of production use (do reach out if it goes down).
A basic frontend is available at the root path (GET /
), or at localhost:3001/
when running locally in dev mode. While the primary intended mechanism of interaction with Smallocator is via the API, the UI serves as a convenient and direct secondary access point.
It supports:
- viewing the health status of the server
- managing session key via EIP-4361 (Sign in with Ethereum)
- depositing Native tokens and ERC20 tokens
- viewing allocatable and allocated balances
- performing allocated transfers and withdrawals
- initiating, executing, and disabling forced withdrawals
Note that the frontend is still relatively unstable (any contributions here are welcome).
GET /health
Example response:
{
"status": "healthy",
"allocatorAddress": "0x1234567890123456789012345678901234567890",
"signingAddress": "0x9876543210987654321098765432109876543210",
"timestamp": "2024-03-07T12:00:00.000Z",
"supportedChains": [
{
"chainId": "1",
"allocatorId": "0x12345678901234567890abcd",
"finalizationThresholdSeconds": 25
},
{
"chainId": "10",
"allocatorId": "0x12345678901234567890abcd",
"finalizationThresholdSeconds": 10
},
{
"chainId": "8453",
"allocatorId": "0x2345678901234567890abcde",
"finalizationThresholdSeconds": 2
}
]
}
All authentication endpoints require a valid session ID in the x-session-id
header.
-
Get Session Payload
GET /session/:chainId/:address
Returns an EIP-4361 payload for signing. The chainId parameter specifies which blockchain network to authenticate for. Example response:
{ "payload": { "domain": "localhost:3000", "address": "0x...", "uri": "http://localhost:3000", "statement": "Sign in to Smallocator", "version": "1", "chainId": 10, "nonce": "unique_nonce", "issuedAt": "2024-12-03T12:00:00Z", "expirationTime": "2024-12-03T13:00:00Z" } }
-
Create Session
POST /session
Submit the signed payload to create a session. Example request:
{ "signature": "0x1234...7890", "payload": { "domain": "localhost:3000", "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "uri": "http://localhost:3000", "statement": "Sign in to Smallocator", "version": "1", "chainId": 1, "nonce": "d6e1c0c4-3d78-4daa-9e57-5485b7c8c6c3", "issuedAt": "2024-03-07T12:00:00.000Z", "expirationTime": "2024-03-07T13:00:00.000Z", "resources": ["http://localhost:3000/resources"] } }
Returns a session ID for subsequent requests.
-
Get Session
GET /session
Requires a valid session ID in the
x-session-id
header.Example response:
{ "session": { "id": "550e8400-e29b-41d4-a716-446655440000", "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "expiresAt": "2024-03-07T13:30:00Z" } }
-
Delete Session
DELETE /session
Requires a valid session ID in the
x-session-id
header.Example response:
{ "success": true }
-
Submit Compact
POST /compact
Requires a valid session ID in the
x-session-id
header.Example request:
{ "chainId": "10", "compact": { "arbiter": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "sponsor": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "nonce": "0x70997970C51812dc3A010C7d01b50e0d17dc79C800000000000000000000001", "expires": "1732520000", "id": "0x300000000000000000000000000000000000000000000000000000000000001c", "amount": "1000000000000000000", "witnessTypeString": "ExampleWitness exampleWitness)ExampleWitness(uint256 foo, bytes32 bar)", "witnessHash": "0x0000000000000000000000000000000000000000000000000000000000000123" } }
Example response:
{ "hash": "0x1234567890123456789012345678901234567890123456789012345678901234", "signature": "0x1234...7890", "nonce": "0x70997970C51812dc3A010C7d01b50e0d17dc79C800000000000000000000001" }
Note that nonce
can be provided as null
in which case the next available valid nonce will be automatically assigned. witnessTypeString
and witnessHash
can also be null
in which case the attested compact will not incorporate witness data (values must be provided for both or omitted for both to be considered valid).
-
Get Compacts by Address
GET /compacts
Requires a valid session ID in the
x-session-id
header.Example response:
[ { "chainId": "10", "hash": "0x1234567890123456789012345678901234567890123456789012345678901234", "compact": { "arbiter": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "sponsor": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "nonce": "0x70997970C51812dc3A010C7d01b50e0d17dc79C800000000000000000000001", "expires": "1732520000", "id": "0x300000000000000000000000000000000000000000000000000000000000001c", "amount": "1000000000000000000", "witnessTypeString": "ExampleWitness exampleWitness)ExampleWitness(uint256 foo, bytes32 bar)", "witnessHash": "0x0000000000000000000000000000000000000000000000000000000000000123" }, "signature": "0x1234...7890", "createdAt": "2024-03-07T12:00:00Z" } ]
-
Get Specific Compact
GET /compact/:chainId/:claimHash
Requires a valid session ID in the
x-session-id
header.Example response:
{ "chainId": "10", "hash": "0x1234567890123456789012345678901234567890123456789012345678901234", "compact": { "arbiter": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "sponsor": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "nonce": "0x70997970C51812dc3A010C7d01b50e0d17dc79C800000000000000000000001", "expires": "1732520000", "id": "0x300000000000000000000000000000000000000000000000000000000000001c", "amount": "1000000000000000000", "witnessTypeString": "ExampleWitness exampleWitness)ExampleWitness(uint256 foo, bytes32 bar)", "witnessHash": "0x0000000000000000000000000000000000000000000000000000000000000123" }, "signature": "0x1234...7890", "createdAt": "2024-03-07T12:00:00Z" }
-
Get Resource Lock Balance
GET /balance/:chainId/:lockId
Requires a valid session ID in the
x-session-id
header.Returns balance information for a specific resource lock. Example response:
{ "allocatableBalance": "1000000000000000000", "allocatedBalance": "500000000000000000", "balanceAvailableToAllocate": "500000000000000000", "withdrawalStatus": 0 }
The
allocatableBalance
will be the current balance minus the sum of any unfinalized deposits or inbound transfers. The period after which these are considered finalized is configurable for each chain.The
allocatedBalance
will be the sum of any submitted compacts that:- have not been processed (as confirmed by a
Claim
event with the respective claim hash in a finalized block) - have not expired (as confirmed by a finalized block with a timestamp exceeding the
expires
value)
The
balanceAvailableToAllocate
will be:"0"
ifwithdrawalStatus
is non-zero"0"
ifallocatedBalance
>=allocatableBalance
allocatableBalance - allocatedBalance
otherwise
- have not been processed (as confirmed by a
-
Get All Resource Lock Balances
GET /balances
Requires a valid session ID in the
x-session-id
header.Returns balance information for all resource locks managed by this allocator. Example response:
{ "balances": [ { "chainId": "1", "lockId": "0x1234567890123456789012345678901234567890123456789012345678901234", "allocatableBalance": "1000000000000000000", "allocatedBalance": "500000000000000000", "balanceAvailableToAllocate": "500000000000000000", "withdrawalStatus": 0 } ] }
Each balance entry follows the same rules as the single balance endpoint.
-
Get Suggested Nonce
GET /suggested-nonce/:chainId
Requires a valid session ID in the
x-session-id
header.Returns the next available unused nonce for the account on the session for creating new compacts on a specific chain.
Example response:
{ "nonce": "0x70997970C51812dc3A010C7d01b50e0d17dc79C800000000000000000000001" }
- Node.js >= 18
- pnpm >= 9.14.1
- TypeScript >= 5.2
### Configuration & Installation ###
# 1. Clone this repo and enter cloned directory
git clone [email protected]:Uniswap/smallocator.git && cd smallocator
# 2. Copy example environment file (modify as needed)
cp .env.example .env
# 3. Install frontend and backend dependencies
pnpm install:all
# 4. Run tests
pnpm test
### Usage ###
# Run both frontend and backend in development mode with hot reload
pnpm dev:all
# Run tests
pnpm test
# Type checking
pnpm type-check
# Linting
pnpm lint
# Format code
pnpm format
# Build frontend and backend for production
pnpm build:all
# Start production server
pnpm start
The project utilizes Jest to implement various test suites:
- Unit tests for core functionality
- Integration tests for API endpoints
- Validation tests for compact messages
Run all tests with:
pnpm test
The project uses:
- ESLint for code linting
- Prettier for code formatting
- Husky for git hooks
- lint-staged for pre-commit checks
Pre-commit hooks ensure:
- Code is properly formatted
- Tests pass
- No TypeScript errors
- No ESLint warnings
MIT