Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 43 additions & 27 deletions app/vibenet/demos/validity/ValidityDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import { ValidityJson } from './components/ValidityJson';
import {
amountInForVibe,
amountOutAtLimit,
encodeHelperSwap,
encodeHelperSwapExactIn,
encodeHelperSwapExactOut,
fillQuoteFromPairLogs,
fillQuoteFromSwapReceipt,
getReserves,
Expand Down Expand Up @@ -59,7 +60,6 @@ import {
clampToCondition,
formatTokenAmount,
quoteWad,
swapOuts,
tokenInFor,
USDV_DECIMALS,
USDV_SYMBOL,
Expand Down Expand Up @@ -683,32 +683,48 @@ function ValidityDemoInner() {
const side: Side = draft.side;
const tokenIn = tokenInFor(state.deployment, side === 'sell');
try {
const amountIn = amountInForVibe(TRADE_VIBE, side, k, draft.priceWad);
if (amountIn === 0n) throw new Error('Swap size is too small.');
const inventory = await tokenBalance(publicClient, tokenIn, acct.address);
if (inventory < amountIn) {
throw new Error(
side === 'sell'
? `Need ${formatTokenAmount(TRADE_VIBE)} ${VIBE_SYMBOL} to sell.`
: `Need ${formatTokenAmount(amountIn, USDV_DECIMALS)} ${USDV_SYMBOL} to buy ${formatTokenAmount(TRADE_VIBE)} ${VIBE_SYMBOL}.`,
);
// Both sides trade exactly TRADE_VIBE VIBE, filled at the on-chain price
// when the predicate lets the tx in. Sell = exact-in (put in 100 VIBE,
// take >= the limit USDV); buy = exact-out (take exactly 100 VIBE, spend
// <= the limit USDV). The helper computes the variable leg on-chain, so
// neither leaves a surplus in the pool.
let call: ReturnType<typeof encodeHelperSwapExactIn>;
if (side === 'sell') {
const amountIn = TRADE_VIBE;
const inventory = await tokenBalance(publicClient, tokenIn, acct.address);
if (inventory < amountIn) {
throw new Error(`Need ${formatTokenAmount(TRADE_VIBE)} ${VIBE_SYMBOL} to sell.`);
}
const outAtLimit = amountOutAtLimit(amountIn, 'sell', k, draft.priceWad);
const minOut = outAtLimit > 1n ? outAtLimit - 1n : outAtLimit;
if (minOut === 0n) throw new Error('Swap size is too small.');
call = encodeHelperSwapExactIn({
helper: state.deployment.helper,
tokenIn,
pair: state.deployment.pair,
amountIn,
minOut,
});
} else {
// Cap the input at the limit-priced USDV, plus a hair (0.1% + 1 wei) so
// integer rounding at the limit edge can't trip MAX_IN.
const limitIn = amountInForVibe(TRADE_VIBE, 'buy', k, draft.priceWad);
if (limitIn === 0n) throw new Error('Swap size is too small.');
const maxIn = limitIn + limitIn / 1000n + 1n;
const inventory = await tokenBalance(publicClient, tokenIn, acct.address);
if (inventory < maxIn) {
throw new Error(
`Need ${formatTokenAmount(maxIn, USDV_DECIMALS)} ${USDV_SYMBOL} to buy ${formatTokenAmount(TRADE_VIBE)} ${VIBE_SYMBOL}.`,
);
}
call = encodeHelperSwapExactOut({
helper: state.deployment.helper,
tokenIn,
pair: state.deployment.pair,
amountOut: TRADE_VIBE,
maxIn,
});
}
const outExact = amountOutAtLimit(amountIn, side, k, draft.priceWad);
const out = outExact > 1n ? outExact - 1n : outExact;
if (out === 0n) throw new Error('Swap size is too small.');
const { amount0Out, amount1Out } = swapOuts({
vibeToken0,
sellVibe: side === 'sell',
amountOut: out,
});
const call = encodeHelperSwap({
helper: state.deployment.helper,
tokenIn,
pair: state.deployment.pair,
amountIn,
amount0Out,
amount1Out,
});
const seconds =
submitMode === 'concurrent'
? clampNoncelessExpiry(expirySeconds)
Expand Down
38 changes: 33 additions & 5 deletions app/vibenet/demos/validity/lib/amm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,20 +214,48 @@ export function encodeApprove(token: Address, spender: Address): { to: Address;
};
}

export function encodeHelperSwap(args: {
/**
* Exact-in swap through the 0-fee SwapHelper (used for sells): commit `amountIn`,
* receive the full on-chain-computed output (>= `minOut`). The output is derived
* from live reserves in the same tx, so nothing is left in the pool and there is
* no `UniswapV2: K` revert from a stale quote.
*/
export function encodeHelperSwapExactIn(args: {
helper: Address;
tokenIn: Address;
pair: Address;
amountIn: bigint;
amount0Out: bigint;
amount1Out: bigint;
minOut: bigint;
}): { to: Address; data: Hex } {
return {
to: args.helper,
data: encodeFunctionData({
abi: helperAbi,
functionName: 'swapExactIn',
args: [args.tokenIn, args.pair, args.amountIn, args.minOut],
}),
};
}

/**
* Exact-out swap through the 0-fee SwapHelper (used for buys): receive exactly
* `amountOut`, spending the on-chain-computed input (<= `maxIn`). The helper
* pulls only the input required at live reserves, so a buy of exactly N VIBE
* never leaves a surplus in the pool.
*/
export function encodeHelperSwapExactOut(args: {
helper: Address;
tokenIn: Address;
pair: Address;
amountOut: bigint;
maxIn: bigint;
}): { to: Address; data: Hex } {
return {
to: args.helper,
data: encodeFunctionData({
abi: helperAbi,
functionName: 'swap',
args: [args.tokenIn, args.pair, args.amountIn, args.amount0Out, args.amount1Out],
functionName: 'swapExactOut',
args: [args.tokenIn, args.pair, args.amountOut, args.maxIn],
}),
};
}
Expand Down
74 changes: 73 additions & 1 deletion app/vibenet/demos/validity/lib/artifacts/SwapHelper.json
Original file line number Diff line number Diff line change
@@ -1 +1,73 @@
{"abi":[{"type":"function","name":"swap","inputs":[{"name":"tokenIn","type":"address","internalType":"address"},{"name":"pair","type":"address","internalType":"address"},{"name":"amountIn","type":"uint256","internalType":"uint256"},{"name":"amount0Out","type":"uint256","internalType":"uint256"},{"name":"amount1Out","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"}],"bytecode":"0x608060405234801561000f575f80fd5b506102298061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80637a950f991461002d575b5f80fd5b61004061003b366004610184565b610042565b005b6040516323b872dd60e01b81523360048201526001600160a01b038581166024830152604482018590528616906323b872dd906064016020604051808303815f875af1158015610094573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906100b891906101cd565b6100f35760405162461bcd60e51b81526020600482015260086024820152672a2920a729a322a960c11b604482015260640160405180910390fd5b60405163022c0d9f60e01b81526004810183905260248101829052336044820152608060648201525f60848201526001600160a01b0385169063022c0d9f9060a4015f604051808303815f87803b15801561014c575f80fd5b505af115801561015e573d5f803e3d5ffd5b505050505050505050565b80356001600160a01b038116811461017f575f80fd5b919050565b5f805f805f60a08688031215610198575f80fd5b6101a186610169565b94506101af60208701610169565b94979496505050506040830135926060810135926080909101359150565b5f602082840312156101dd575f80fd5b815180151581146101ec575f80fd5b939250505056fea2646970667358221220543bb5afcba8324e91fa3066ed0aabc3ba64dc9364a70aec6466d23a776947e264736f6c63430008140033"}
{
"abi": [
{
"type": "function",
"name": "swapExactIn",
"inputs": [
{
"name": "tokenIn",
"type": "address",
"internalType": "address"
},
{
"name": "pair",
"type": "address",
"internalType": "address"
},
{
"name": "amountIn",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "minOut",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "out",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "swapExactOut",
"inputs": [
{
"name": "tokenIn",
"type": "address",
"internalType": "address"
},
{
"name": "pair",
"type": "address",
"internalType": "address"
},
{
"name": "amountOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "maxIn",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "amountIn",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "nonpayable"
}
],
"bytecode": "0x6080604052348015600e575f80fd5b506107468061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c806310b8462314610038578063faf725671461005d575b5f80fd5b61004b61004636600461055d565b610070565b60405190815260200160405180910390f35b61004b61006b36600461055d565b61025c565b5f805f8061007e8888610423565b9194509250905061008f86846105b4565b61009983886105cd565b6100a391906105e4565b9350848410156100f05760405162461bcd60e51b815260206004820152601360248201527214ddd85c12195b1c195c8e8813525397d3d555606a1b60448201526064015b60405180910390fd5b6040516323b872dd60e01b81523360048201526001600160a01b038881166024830152604482018890528916906323b872dd906064016020604051808303815f875af1158015610142573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101669190610603565b6101ae5760405162461bcd60e51b81526020600482015260196024820152785377617048656c7065723a205452414e534645525f46524f4d60381b60448201526064016100e7565b5f80826101bc57855f6101bf565b5f865b90925090506001600160a01b03891663022c0d9f8383335f5b6040519080825280601f01601f191660200182016040528015610202576020820181803683370190505b506040518563ffffffff1660e01b81526004016102229493929190610629565b5f604051808303815f87803b158015610239575f80fd5b505af115801561024b573d5f803e3d5ffd5b505050505050505050949350505050565b5f805f8061026a8888610423565b9250925092508186106102ca5760405162461bcd60e51b815260206004820152602260248201527f5377617048656c7065723a20494e53554646494349454e545f4c495155494449604482015261545960f01b60648201526084016100e7565b6102d4868361067b565b6102de87856105cd565b6102e891906105e4565b6102f39060016105b4565b93508484111561033a5760405162461bcd60e51b815260206004820152601260248201527129bbb0b82432b63832b91d1026a0ac2fa4a760711b60448201526064016100e7565b6040516323b872dd60e01b81523360048201526001600160a01b038881166024830152604482018690528916906323b872dd906064016020604051808303815f875af115801561038c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103b09190610603565b6103f85760405162461bcd60e51b81526020600482015260196024820152785377617048656c7065723a205452414e534645525f46524f4d60381b60448201526064016100e7565b5f808261040657875f6101bf565b505f9050866001600160a01b03891663022c0d9f838333826101d8565b5f805f805f856001600160a01b0316630902f1ac6040518163ffffffff1660e01b8152600401606060405180830381865afa158015610464573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061048891906106a9565b5091509150856001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104c9573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104ed91906106f5565b6001600160a01b0316876001600160a01b03161492508261052157806001600160701b0316826001600160701b0316610536565b816001600160701b0316816001600160701b03165b9098909750929550919350505050565b6001600160a01b038116811461055a575f80fd5b50565b5f805f8060808587031215610570575f80fd5b843561057b81610546565b9350602085013561058b81610546565b93969395505050506040820135916060013590565b634e487b7160e01b5f52601160045260245ffd5b808201808211156105c7576105c76105a0565b92915050565b80820281158282048414176105c7576105c76105a0565b5f826105fe57634e487b7160e01b5f52601260045260245ffd5b500490565b5f60208284031215610613575f80fd5b81518015158114610622575f80fd5b9392505050565b84815283602082015260018060a01b0383166040820152608060608201525f8251806080840152806020850160a085015e5f60a0828501015260a0601f19601f83011684010191505095945050505050565b818103818111156105c7576105c76105a0565b80516001600160701b03811681146106a4575f80fd5b919050565b5f805f606084860312156106bb575f80fd5b6106c48461068e565b92506106d26020850161068e565b9150604084015163ffffffff811681146106ea575f80fd5b809150509250925092565b5f60208284031215610705575f80fd5b81516106228161054656fea264697066735822122033045389fa8bd7949133dfb70f345f11e6379452902dc70bf9596232f4bbe8bc64736f6c634300081a0033"
}
36 changes: 21 additions & 15 deletions app/vibenet/demos/validity/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { parseAbi, type Abi, type Hex } from 'viem';
import { parseAbi } from 'viem';

import helperArtifact from './artifacts/SwapHelper.json';
import factoryArtifact from './artifacts/UniswapV2Factory.json';
import minterArtifact from './artifacts/ValidityOpenMinter.json';

function with0x(value: string): Hex {
return (value.startsWith('0x') ? value : `0x${value}`) as Hex;
}
// Minimal ABI fragments — only the functions the client calls. The singleton
// addresses are hardcoded (see singleton.ts), so we no longer import the
// compiled artifacts (or their creation bytecode) here; the bytecode is
// vendored in base/vibenet's setup bytecode manifests for deployment.

export const erc20Abi = parseAbi([
'function name() view returns (string)',
Expand All @@ -19,8 +16,10 @@ export const erc20Abi = parseAbi([
'function mint(address,uint256)',
]);

export const factoryAbi = factoryArtifact.abi as Abi;
export const factoryBytecode = with0x(factoryArtifact.bytecode);
export const factoryAbi = parseAbi([
'function allPairsLength() view returns (uint256)',
'function allPairs(uint256) view returns (address)',
]);

export const pairAbi = parseAbi([
'function token0() view returns (address)',
Expand All @@ -30,15 +29,22 @@ export const pairAbi = parseAbi([
'function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes data)',
]);

export const helperAbi = helperArtifact.abi as Abi;
export const helperBytecode = with0x(helperArtifact.bytecode);
// SwapHelper: 0-fee, computes the variable leg on-chain. exact-in for sells
// (spend amountIn, receive >= minOut); exact-out for buys (receive exactly
// amountOut, spend <= maxIn). See singleton.ts.
export const helperAbi = parseAbi([
'function swapExactIn(address tokenIn, address pair, uint256 amountIn, uint256 minOut) returns (uint256)',
'function swapExactOut(address tokenIn, address pair, uint256 amountOut, uint256 maxIn) returns (uint256)',
]);

export const minterAbi = minterArtifact.abi as Abi;
export const minterBytecode = with0x(minterArtifact.bytecode);
// ValidityOpenMinter: relay holding VIBE's B20 MINT_ROLE.
export const minterAbi = parseAbi([
'function mint(address token, address to, uint256 amount)',
]);

export const CANDLES_PATH = '/api/vibenet/validity/candles';

export const STORAGE_KEY = 'vibenet.validity.v6';
export const STORAGE_KEY = 'vibenet.validity.v7';

export const WAD = 10n ** 18n;
export const USDV_DECIMALS = 6;
Expand Down
22 changes: 0 additions & 22 deletions app/vibenet/demos/validity/lib/singleton.test.ts

This file was deleted.

Loading
Loading