Fee configuration rule engine for NEAR Intents 1Click API
npm install intents-1click-rule-engineFee configuration rule engine for NEAR Intents 1Click API. Matches swap requests against configurable rules to determine fees.
``bash`
bun add intents-1click-rule-engine
`typescript
import { RuleEngine, getTotalBps, calculateFee, type FeeConfig } from "intents-1click-rule-engine";
const feeConfig: FeeConfig = {
version: "1.0.0",
default_fee: { type: "bps", bps: 20, recipient: "fees.near" },
rules: [
{
id: "usdc-swaps",
enabled: true,
priority: 100,
match: {
in: { symbol: "USDC", blockchain: "*" },
out: { symbol: "USDC", blockchain: "*" },
},
fee: { type: "bps", bps: 10, recipient: "fees.near" },
},
],
};
// Create engine (validates config, throws on error)
const engine = new RuleEngine(feeConfig);
// Initialize token registry
await engine.initialize();
// Match a swap request
const result = engine.match({
originAsset: "nep141:eth-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.omft.near",
destinationAsset: "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
});
console.log(result.matched); // true
console.log(result.fee); // { type: "bps", bps: 10, recipient: "fees.near" }
console.log(result.rule?.id); // "usdc-swaps"
console.log(getTotalBps(result.fee)); // 10
// Calculate fee amount
const feeAmount = calculateFee("1000000", getTotalBps(result.fee)); // "1000" (0.10% of 1000000)
`
The calculateFee and calculateAmountAfterFee functions validate their inputs:
`typescript
// Valid usage
calculateFee("1000000", 20); // "2000" (0.20% of 1000000)
calculateFee(1000000n, 20); // "2000" (bigint input also works)
calculateAmountAfterFee("1000000", 20); // "998000" (amount minus fee)
// Input validation - these throw descriptive errors:
calculateFee("abc", 20); // Error: not a valid integer string
calculateFee("12.5", 20); // Error: not a valid integer string
calculateFee("-100", 20); // Error: must be non-negative
calculateFee("1000", -5); // Error: bps must be non-negative
calculateFee("1000", 10001); // Error: bps exceeds maximum of 10000 (100%)
calculateFee("1000", 20.5); // Error: bps must be an integer
`
The bps parameter must be between 0 and 10000 (0% to 100%).
The engine fetches the token list from https://1click.chaindefuser.com/v0/tokens to resolve asset IDs to their blockchain and symbol. This allows rules to match by symbol/blockchain instead of exact asset IDs.
``
Swap Request Token Registry Lookup
───────────── ─────────────────────
originAsset: "nep141:eth-..." → { blockchain: "eth", symbol: "USDC" }
destinationAsset: "nep141:sol-..." → { blockchain: "sol", symbol: "USDC" }
Call engine.initialize() before matching to fetch the token list. The list is cached for 1 hour by default.
Rules are evaluated by priority (highest first). The first matching rule wins. If no rules match, default_fee is used.
Each rule can match on:
- assetId - exact asset identifier (string or array)blockchain
- - chain identifier (e.g., "eth", "polygon")symbol
- - token symbol (e.g., "USDC", "WBTC")
Special patterns:
- "*" - wildcard, matches any value"!value"
- - negation, matches anything except value["a", "b"]
- - array, matches any value in the list (OR logic)
Important: All pattern matching is case-sensitive. "USDC" will not match "usdc" or "Usdc". Ensure your rules use the exact casing from the token registry.
Each fee requires a type, bps, and recipient. Fees can be a single object or an array for multiple recipients:
`typescript`
fee: { type: "bps", bps: 20, recipient: "fees.near" }
Each recipient can have their own fee amount:
`typescript`
fee: [
{ type: "bps", bps: 14, recipient: "fees.near" }, // 0.14%
{ type: "bps", bps: 6, recipient: "partner.near" }, // 0.06%
]
// Total: 0.20%
`typescript`
{
id: "usdc-to-usdc",
enabled: true,
priority: 100,
match: {
in: { symbol: "USDC" },
out: { symbol: "USDC" },
},
fee: { type: "bps", bps: 10, recipient: "fees.near" },
}
`typescript`
{
id: "eth-to-polygon",
enabled: true,
priority: 100,
match: {
in: { blockchain: "eth" },
out: { blockchain: "polygon" },
},
fee: { type: "bps", bps: 15, recipient: "fees.near" },
}
`typescript`
{
id: "eth-usdc-to-polygon-usdc",
enabled: true,
priority: 200,
match: {
in: { assetId: "nep141:eth-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.omft.near" },
out: { assetId: "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near" },
},
fee: { type: "bps", bps: 5, recipient: "fees.near" },
}
`typescript`
{
id: "eth-usdc-to-any-usdc",
enabled: true,
priority: 150,
match: {
in: { blockchain: "eth", symbol: "USDC" },
out: { symbol: "USDC", blockchain: "*" },
},
fee: { type: "bps", bps: 8, recipient: "fees.near" },
}
`typescript`
{
id: "anything-to-solana",
enabled: true,
priority: 50,
match: {
in: { blockchain: "*" },
out: { blockchain: "sol" },
},
fee: { type: "bps", bps: 25, recipient: "fees.near" },
}
`typescript`
{
id: "non-eth-swaps",
enabled: true,
priority: 100,
match: {
in: { blockchain: "!eth" },
out: { blockchain: "!eth" },
},
fee: { type: "bps", bps: 12, recipient: "fees.near" },
}
`typescript`
{
id: "stablecoin-swaps",
enabled: true,
priority: 100,
match: {
in: { symbol: ["USDC", "USDT", "DAI"] },
out: { symbol: ["USDC", "USDT", "DAI"] },
},
fee: { type: "bps", bps: 5, recipient: "fees.near" },
}
`typescript`
{
id: "l2-to-l2",
enabled: true,
priority: 100,
match: {
in: { blockchain: ["arb", "polygon", "base", "op"] },
out: { blockchain: ["arb", "polygon", "base", "op"] },
},
fee: { type: "bps", bps: 8, recipient: "fees.near" },
}
`typescript`
{
id: "specific-usdc-tokens",
enabled: true,
priority: 150,
match: {
in: {
assetId: [
"nep141:eth-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.omft.near",
"nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near",
]
},
out: { symbol: "USDC" },
},
fee: { type: "bps", bps: 5, recipient: "fees.near" },
}
Rules can have valid_from and valid_until timestamps. Dates must be valid ISO 8601 strings - invalid dates will throw an error during matching.
`typescript`
{
id: "new-year-promo",
enabled: true,
priority: 200,
valid_from: "2025-01-01T00:00:00Z",
valid_until: "2025-01-07T23:59:59Z",
match: {
in: { symbol: "*" },
out: { symbol: "*" },
},
fee: { type: "bps", bps: 0, recipient: "fees.near" },
}
`typescript`
{
id: "summer-discount",
enabled: true,
priority: 150,
valid_from: "2025-06-01T00:00:00Z",
// No valid_until - runs indefinitely after start
match: {
in: { symbol: "USDC" },
out: { symbol: "USDC" },
},
fee: { type: "bps", bps: 5, recipient: "fees.near" },
}
Split fees between multiple accounts (e.g., platform + partner):
`typescript`
{
id: "partner-referral",
enabled: true,
priority: 100,
match: {
in: { symbol: "USDC" },
out: { symbol: "USDC" },
},
fee: [
{ type: "bps", bps: 7, recipient: "fees.near" },
{ type: "bps", bps: 3, recipient: "partner.near" },
],
}
// Total fee: 10 bps (0.10%), split 70/30
`typescript`
const feeConfig = {
version: "1.0.0",
default_fee: { type: "bps", bps: 30, recipient: "fees.near" },
rules: [
// Most specific first (higher priority)
{
id: "eth-usdc-to-polygon-usdc",
enabled: true,
priority: 200,
match: {
in: { assetId: "nep141:eth-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.omft.near" },
out: { assetId: "nep141:base-0x833589fcd6edb6e08f4c7c32d4f71b54bda02913.omft.near" },
},
fee: { type: "bps", bps: 5, recipient: "fees.near" },
},
// Chain + symbol combo
{
id: "eth-usdc-to-any",
enabled: true,
priority: 150,
match: {
in: { blockchain: "eth", symbol: "USDC" },
out: { blockchain: "*" },
},
fee: { type: "bps", bps: 12, recipient: "fees.near" },
},
// All stablecoin swaps
{
id: "usdc-swaps",
enabled: true,
priority: 100,
match: {
in: { symbol: "USDC" },
out: { symbol: "USDC" },
},
fee: { type: "bps", bps: 10, recipient: "fees.near" },
},
// Disabled rule (won't match)
{
id: "promo-free-swaps",
enabled: false,
priority: 300,
match: {
in: { blockchain: "*" },
out: { blockchain: "*" },
},
fee: { type: "bps", bps: 0, recipient: "fees.near" },
},
],
};
- Higher priority rules are evaluated first
- First matching rule wins
- Use priority gaps (50, 100, 150, 200) to allow inserting rules later
- More specific rules should have higher priority
A simple browser-based tool for building fee configurations is included:
Online: https://bonnevoyager.github.io/intents-1click-rule-engine/
Local:
`bash`
open index.html
Features:
- Visual rule editor with all matching options
- Support for wildcards, negation, and arrays
- Multiple fee recipients
- Time-based rules
- Import/export JSON configurations
- No dependencies - works offline
`bash`
bun install
bun test
bun run typecheck
1. Create an npm account if you don't have one
2. Login to npm:
`bash`
npm login
1. Update the version in package.json:
`bashPatch release (1.0.0 -> 1.0.1)
npm version patch
2. Run tests to ensure everything works:
`bash
bun test && bun run typecheck
`3. Publish to npm:
`bash
npm publish
`$3
If you want to publish under a scope (e.g.,
@myorg/intents-1click-rule-engine):1. Update the
name in package.json:`json
{
"name": "@myorg/intents-1click-rule-engine"
}
`2. Publish with public access (scoped packages are private by default):
`bash
npm publish --access public
`$3
After publishing, verify the package is available:
`bash
npm info intents-1click-rule-engine
``