Pay with anything. Arrive in whatever is required.
Griffin is an intent-based cross-chain payment protocol. A user pays in any token they hold. The recipient receives exactly the token they need. Griffin handles everything in between — conversion, routing, bridging, and settlement — in a single signed intent.
Crypto payments have a token mismatch problem. A merchant wants USDC. A user holds HSK. An NFT costs a token you don't own. Today, the user stops, navigates to an exchange, swaps manually, returns, and completes the payment. That is four steps too many.
Griffin collapses this to one. The user signs a single intent. Griffin resolves the mismatch and delivers exactly what the recipient asked for.
Griffin follows a strict intent-based model. The user expresses what they want to happen. Griffin determines how to make it happen.
User signs intent
│
▼
IntentService validates + stores
│
▼
SettlementEngine evaluates settlers in priority order
│
├─ InventorySettler → Griffin holds the output token? Send directly.
├─ SwapSettler → Same-chain pair? Route through GriffinDEX.
└─ BridgeSettler → Cross-chain? Route through Superbridge.
│
▼
On-chain transaction submitted + confirmed
│
▼
Intent marked COMPLETED — recipient has their tokens
The user never touches a DEX. The user never bridges manually. The user signs once.
Griffin is a pnpm monorepo with four packages and a utility scripts layer.
Griffin/
├── packages/
│ ├── orchestrator/ # The backend brain — intent processing, routing, settlement
│ ├── sdk/ # Typed HTTP client for dApp developers
│ ├── contracts/ # GriffinDEX AMM + MockERC20 (Hardhat, deployed on Hashkey)
│ └── app/ # Minimal React demo frontend (Vite + wagmi)
├── scripts/
│ └── test-sdk.ts # End-to-end smoke test against a live orchestrator
└── docs/
├── ARCHITECTURE.md
└── RESOURCES.md
The most deliberate architectural decision in Griffin is the settler pattern. Every settlement mechanism — whether it moves tokens from Griffin's own vault, routes through an on-chain AMM, or calls a cross-chain bridge — implements a single interface:
interface ISettler {
readonly type: SettlerType;
canSettle(intent: Intent): Promise<SettleabilityCheck>;
settle(intent: Intent): Promise<SettlementResult>;
}The SettlementEngine holds an ordered list of settlers. For each intent, it calls canSettle() on each settler in sequence. The first one that returns capable: true handles the intent. The engine never knows — or cares — which settler wins.
This means adding a new settlement mechanism is a single file and a one-line registration:
// app.ts — the entire wiring change to add a new settler
const settlementEngine = new SettlementEngine([
new InventorySettler(chainClients, vaultAddress),
new SwapSettler(dexClients, chainClients),
new BridgeSettler(bridgeClients, chainClients, vaultAddress),
new AcrossSettler(...), // ← add this, nothing else changes
]);The same pattern extends to the blockchain client layer. Every chain integration implements IChainClient. Every DEX integration implements IDexClient. Every bridge integration implements IBridgeClient. Settlers depend only on these interfaces — never on concrete implementations.
| Settler | Trigger | Mechanism |
|---|---|---|
InventorySettler |
Griffin holds the output token | Direct ERC-20 transfer from Griffin's vault |
SwapSettler |
Same-chain, different tokens | GriffinDEX.swapToRecipient() — constant product AMM |
BridgeSettler |
Cross-chain intent | Superbridge API — multi-step bridge execution |
Inventory is tried first (fastest, no slippage, no external dependency). Swap is tried second (same-chain, on-chain AMM). Bridge is the fallback for cross-chain intents. The priority is explicit and configurable at startup.
The orchestrator is a Node.js/TypeScript Express server. It is the only component that holds private keys and talks to chains. It runs 24/7 and processes intents.
Key modules:
IntentService— creates, validates (including EIP-712 signature verification), and manages intent lifecycleSettlementEngine— settler selection and delegationChainService— registry of supported chains and tokensRouteService— quote generation for cross-chain routesblockchain/— chain client implementations (EvmClient,DexClient,SuperbridgeClient) behind clean interfaces (IChainClient,IDexClient,IBridgeClient)settlement/— settler implementations (InventorySettler,SwapSettler,BridgeSettler)
Test coverage: 170+ unit tests across all services, settlers, routes, and middleware. Tests run against mock interfaces — no real chain, no real API key required.
A typed HTTP client that wraps the orchestrator's REST API. Zero dependencies beyond native fetch. Works in Node 18+, browsers, and edge runtimes.
import { GriffinClient } from "@griffin/sdk";
const griffin = new GriffinClient({ baseUrl: "https://your-orchestrator.xyz" });
const intent = await griffin.createIntent({
fromChain: "eip155:133",
toChain: "eip155:1",
fromToken: "0x...",
toToken: "0x...",
amount: "10",
recipient: "0x...",
userAddress: "0x...",
requestMessage: signedMessage,
requestSignature: signature,
});
await griffin.executeIntent(intent.intentId);The SDK is a workspace package — no npm publish required. Any app in the monorepo imports it directly via a Vite alias.
Hardhat project deployed on Hashkey Testnet (chain ID 133).
GriffinDEX.sol— a minimal constant-product AMM (Uniswap v2 style) with aswapToRecipient(tokenIn, tokenOut, amountIn, minAmountOut, recipient)function. This is Griffin's core primitive: the sender pays, the recipient receives, in one atomic transaction.MockERC20.sol— configurable test token (name, symbol, decimals, initial supply). Used to seed liquidity pools and fund Griffin's vault for demos.
Deployed contracts (Hashkey Testnet):
GriffinDEX:0x16279052BFEde721ed2662F41A754966a3E48124tHSK(Test HSK, 18 decimals):0xb8F355f10569FD2A765296161d082Cc37c5843c2tUSDC(Test USDC, 6 decimals):0xc4C2841367016C9e2652Fecc49bBA9229787bA82
A minimal Vite + React application that demonstrates the full Griffin flow in a browser.
- Connects to MetaMask via wagmi
- Prompts the user to sign an EIP-712 typed data message authorising the intent
- Submits the intent to the orchestrator via the SDK
- Polls for completion and shows the result
The frontend is intentionally minimal — its purpose is to prove the integration story, not to be a production UI.
Every chain, DEX, and bridge is hidden behind an interface. Settlers never import concrete clients.
IChainClient IDexClient IBridgeClient
│ │ │
▼ ▼ ▼
EvmClient DexClient SuperbridgeClient
StellarClient* (GriffinDEX) (+ AcrossClient, HopClient...)
StarknetClient*
* = stubbed, ready for implementation
Adding Starknet support means creating StarknetClient implements IChainClient. The settlers, the engine, and the API don't change.
Every intent requires a user signature. Griffin uses EIP-712 typed data — not a raw message hash — so MetaMask shows the user a structured, human-readable authorisation prompt:
Sign this data:
fromToken: 0xb8F355...
toToken: 0xc4C284...
amount: 10
recipient: 0x345b10...
userAddress: 0xB1655b...
nonce: 1713000000000
The orchestrator reconstructs the same typed data from requestMessage and calls ethers.verifyTypedData() to confirm the signature matches userAddress. Replay attacks are prevented by the nonce (timestamp).
End-to-end smoke test against a live orchestrator. Runs the full flow: health check → supported chains → quote → create intent → execute → poll for completion.
USER_ADDRESS=0x... RECIPIENT=0x... npx tsx scripts/test-sdk.tsDeploys MockERC20 tokens and seeds a GriffinDEX liquidity pool in one command.
cd packages/contracts && pnpm deploy:tokensApproves GriffinDEX to spend Griffin's vault tokens (required before the first swap).
cd packages/contracts && pnpm approve:tokens# Install dependencies
pnpm install
# Configure the orchestrator
cp packages/orchestrator/.env.example packages/orchestrator/.env
# Fill in: GRIFFIN_OPERATOR_PRIVATE_KEY, GRIFFIN_VAULT_ADDRESS,
# GRIFFIN_DEX_ADDRESS, SUPERBRIDGE_API_KEY
# Start the orchestrator
cd packages/orchestrator && pnpm dev
# In a second terminal — start the demo app
cd packages/app && pnpm dev
# Run all tests
pnpm testThree parallel jobs run on every push and pull request:
| Job | What it checks |
|---|---|
orchestrator |
TypeScript, lint, format, build, 129 Jest unit tests |
contracts |
Hardhat compile + 21 Solidity tests (GriffinDEX + MockERC20) |
sdk |
TypeScript, build, 22 Vitest unit tests |
Total: 172 automated tests across the full stack.
Interfaces before implementations. Every external dependency — chains, DEXes, bridges — is hidden behind a TypeScript interface. This made the codebase testable from day one and makes it extensible without touching existing code.
Settlers are stateless. All intent state lives on the Intent object. Settlers read it, act on it, and return a result. They never mutate shared state. This makes the settlement layer safe to reason about and trivial to test.
Graceful degradation. If a settler can't handle an intent — missing API key, insufficient liquidity, wrong chain — it declines with a reason. The engine moves to the next settler. Nothing crashes. The user gets a clear error only if every settler declines.
The orchestrator owns the keys. Private keys never leave the server. The SDK is a thin HTTP client. The frontend signs a typed message — it never touches a private key. Griffin's vault is the only wallet that moves funds.
| Layer | Technology |
|---|---|
| Chain | Hashkey Testnet (EVM, chain ID 133) |
| Smart Contracts | Solidity 0.8.20, OpenZeppelin 5, Hardhat |
| Orchestrator | Node.js 20, TypeScript 5, Express 5 |
| Settlement | Custom settler pattern — pluggable by design |
| Bridge | Superbridge API |
| Wallet | MetaMask via wagmi v2 + viem |
| Frontend | Vite + React 18 |
| SDK | Native fetch, zero runtime dependencies |
| Testing | Jest (orchestrator), Hardhat/Chai (contracts), Vitest (SDK) |
| Monorepo | pnpm workspaces |
MIT — see LICENSE.