Initial commit: add .gitignore and README

This commit is contained in:
defiQUG
2026-02-09 21:51:30 -08:00
commit 47f6f2de7b
92 changed files with 15299 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
# Aave v3: Query user positions and reserves
#
# Endpoint: https://api.thegraph.com/subgraphs/name/aave/aave-v3-[chain]
# Replace [chain] with: ethereum, base, arbitrum, etc.
#
# Example queries for:
# - User positions (supplies, borrows)
# - Reserve data
# - Historical data
# Query user position (supplies and borrows)
query GetUserPosition($userAddress: String!) {
user(id: $userAddress) {
id
reserves {
id
reserve {
id
symbol
name
decimals
underlyingAsset
liquidityRate
variableBorrowRate
stableBorrowRate
aToken {
id
}
vToken {
id
}
sToken {
id
}
}
currentATokenBalance
currentStableDebt
currentVariableDebt
principalStableDebt
scaledVariableDebt
liquidityRate
usageAsCollateralEnabledOnUser
reserve {
price {
priceInEth
priceInUsd
}
}
}
}
}
# Query reserve data
query GetReserves($first: Int = 100) {
reserves(
orderBy: totalLiquidity
orderDirection: desc
first: $first
) {
id
symbol
name
decimals
underlyingAsset
pool {
id
}
price {
priceInEth
priceInUsd
}
totalLiquidity
availableLiquidity
totalATokenSupply
totalCurrentVariableDebt
totalStableDebt
liquidityRate
variableBorrowRate
stableBorrowRate
utilizationRate
baseLTVasCollateral
liquidationThreshold
liquidationBonus
reserveLiquidationThreshold
reserveLiquidationBonus
reserveFactor
aToken {
id
}
vToken {
id
}
sToken {
id
}
}
}
# Query user transaction history
query GetUserTransactions($userAddress: String!, $first: Int = 100) {
userTransactions(
where: { user: $userAddress }
orderBy: timestamp
orderDirection: desc
first: $first
) {
id
timestamp
pool {
id
}
user {
id
}
reserve {
symbol
underlyingAsset
}
action
amount
referrer
onBehalfOf
}
}
# Query deposits
query GetDeposits($userAddress: String!, $first: Int = 100) {
deposits(
where: { user: $userAddress }
orderBy: timestamp
orderDirection: desc
first: $first
) {
id
timestamp
user {
id
}
reserve {
symbol
underlyingAsset
}
amount
onBehalfOf
referrer
}
}
# Query borrows
query GetBorrows($userAddress: String!, $first: Int = 100) {
borrows(
where: { user: $userAddress }
orderBy: timestamp
orderDirection: desc
first: $first
) {
id
timestamp
user {
id
}
reserve {
symbol
underlyingAsset
}
amount
borrowRate
borrowRateMode
onBehalfOf
referrer
}
}
# Query repays
query GetRepays($userAddress: String!, $first: Int = 100) {
repays(
where: { user: $userAddress }
orderBy: timestamp
orderDirection: desc
first: $first
) {
id
timestamp
user {
id
}
reserve {
symbol
underlyingAsset
}
amount
useATokens
onBehalfOf
}
}
# Query liquidations
query GetLiquidations($first: Int = 100) {
liquidations(
orderBy: timestamp
orderDirection: desc
first: $first
) {
id
timestamp
pool {
id
}
user {
id
}
collateralReserve {
symbol
underlyingAsset
}
collateralAmount
principalReserve {
symbol
underlyingAsset
}
principalAmount
liquidator
}
}

View File

@@ -0,0 +1,146 @@
# Cross-Protocol Analytics: Query data across multiple protocols
#
# This is a conceptual example showing how you might query multiple subgraphs
# to analyze cross-protocol strategies and positions.
#
# In production, you would:
# 1. Query multiple subgraphs (Uniswap, Aave, etc.)
# 2. Combine the data
# 3. Calculate metrics like:
# - Total TVL across protocols
# - Cross-protocol arbitrage opportunities
# - User positions across protocols
# - Protocol interaction patterns
# Example: Query user's Aave position and Uniswap LP positions
# (This would require querying two separate subgraphs and combining results)
# Query 1: Get user's Aave positions
# (Use Aave subgraph - see aave-positions.graphql)
# Query 2: Get user's Uniswap v3 positions
query GetUserUniswapPositions($userAddress: String!) {
positions(
where: { owner: $userAddress }
first: 100
) {
id
owner
pool {
id
token0 {
symbol
}
token1 {
symbol
}
feeTier
}
liquidity
depositedToken0
depositedToken1
withdrawnToken0
withdrawnToken1
collectedFeesToken0
collectedFeesToken1
transaction {
timestamp
}
}
}
# Query 3: Get protocol volumes (for analytics)
query GetProtocolVolumes {
# Uniswap volume (example)
uniswapDayDatas(
orderBy: date
orderDirection: desc
first: 30
) {
date
dailyVolumeUSD
totalVolumeUSD
tvlUSD
}
# Aave volume (example - would need Aave subgraph)
# aaveDayDatas {
# date
# dailyDepositsUSD
# dailyBorrowsUSD
# totalValueLockedUSD
# }
}
# Query 4: Get token prices across protocols
query GetTokenPrices($tokenAddress: String!) {
# Uniswap price
token(id: $tokenAddress) {
id
symbol
name
decimals
derivedETH
poolCount
totalValueLocked
totalValueLockedUSD
volume
volumeUSD
feesUSD
txCount
pools {
id
token0 {
symbol
}
token1 {
symbol
}
token0Price
token1Price
totalValueLockedUSD
}
}
# Aave reserve price (would need Aave subgraph)
# reserve(id: $tokenAddress) {
# id
# symbol
# price {
# priceInUsd
# }
# }
}
# Query 5: Get arbitrage opportunities
# (Conceptual - would require real-time price comparison)
query GetArbitrageOpportunities {
# Get pools with significant price differences
# This is a simplified example - real arbitrage detection is more complex
pools(
where: {
# Filter by high volume and liquidity
totalValueLockedUSD_gt: "1000000"
volumeUSD_gt: "100000"
}
orderBy: volumeUSD
orderDirection: desc
first: 50
) {
id
token0 {
symbol
}
token1 {
symbol
}
token0Price
token1Price
feeTier
volumeUSD
tvlUSD
# Compare with prices from other DEXes/AMMs
# (would require additional queries)
}
}

View File

@@ -0,0 +1,137 @@
# Uniswap v3: Query pool data and swap information
#
# Endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3
#
# Example queries for:
# - Pool information
# - Token prices
# - Swap history
# - Liquidity data
# Query pool by token pair
query GetPoolByPair($token0: String!, $token1: String!, $fee: BigInt!) {
pools(
where: {
token0: $token0,
token1: $token1,
feeTier: $fee
}
orderBy: totalValueLockedUSD
orderDirection: desc
first: 1
) {
id
token0 {
id
symbol
name
decimals
}
token1 {
id
symbol
name
decimals
}
feeTier
liquidity
sqrtPrice
tick
token0Price
token1Price
volumeUSD
tvlUSD
totalValueLockedUSD
}
}
# Query swap history for a pool
query GetPoolSwaps($poolId: String!, $first: Int = 100) {
swaps(
where: { pool: $poolId }
orderBy: timestamp
orderDirection: desc
first: $first
) {
id
timestamp
transaction {
id
blockNumber
}
pool {
id
token0 {
symbol
}
token1 {
symbol
}
}
sender
recipient
amount0
amount1
amountUSD
sqrtPriceX96
tick
}
}
# Query pool day data for historical analysis
query GetPoolDayData($poolId: String!, $days: Int = 30) {
poolDayDatas(
where: { pool: $poolId }
orderBy: date
orderDirection: desc
first: $days
) {
id
date
pool {
id
token0 {
symbol
}
token1 {
symbol
}
}
liquidity
sqrtPrice
token0Price
token1Price
volumeUSD
tvlUSD
feesUSD
open
high
low
close
}
}
# Query top pools by TVL
query GetTopPoolsByTVL($first: Int = 10) {
pools(
orderBy: totalValueLockedUSD
orderDirection: desc
first: $first
) {
id
token0 {
symbol
name
}
token1 {
symbol
name
}
feeTier
liquidity
volumeUSD
tvlUSD
totalValueLockedUSD
}
}

View File

@@ -0,0 +1,116 @@
/**
* Aave v3: Multi-asset flash loan
*
* This example demonstrates how to execute a flash loan for multiple assets.
* Useful for arbitrage opportunities across multiple tokens.
*/
import { createWalletRpcClient } from '../../src/utils/chain-config.js';
import { getAavePoolAddress } from '../../src/utils/addresses.js';
import { getTokenMetadata, parseTokenAmount } from '../../src/utils/tokens.js';
import { waitForTransaction } from '../../src/utils/rpc.js';
import type { Address, Hex } from 'viem';
const CHAIN_ID = 1; // Mainnet
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
// Aave Pool ABI
const POOL_ABI = [
{
name: 'flashLoan',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'receiverAddress', type: 'address' },
{ name: 'assets', type: 'address[]' },
{ name: 'amounts', type: 'uint256[]' },
{ name: 'modes', type: 'uint256[]' },
{ name: 'onBehalfOf', type: 'address' },
{ name: 'params', type: 'bytes' },
{ name: 'referralCode', type: 'uint16' },
],
outputs: [],
},
] as const;
/**
* Flash loan modes:
* 0: No debt (just flash loan, repay fully)
* 1: Stable debt (deprecated in v3.3+)
* 2: Variable debt (open debt position)
*/
const FLASH_LOAN_MODE_NO_DEBT = 0;
const FLASH_LOAN_MODE_VARIABLE_DEBT = 2;
const FLASH_LOAN_RECEIVER = process.env.FLASH_LOAN_RECEIVER as `0x${string}`;
async function flashLoanMulti() {
const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY);
const publicClient = walletClient as any;
const account = walletClient.account?.address;
if (!account) {
throw new Error('No account available');
}
const poolAddress = getAavePoolAddress(CHAIN_ID);
// Multiple tokens for flash loan
const tokens = [
getTokenMetadata(CHAIN_ID, 'USDC'),
getTokenMetadata(CHAIN_ID, 'USDT'),
];
const amounts = [
parseTokenAmount('10000', tokens[0].decimals), // 10,000 USDC
parseTokenAmount('5000', tokens[1].decimals), // 5,000 USDT
];
const assets = tokens.map(t => t.address);
const modes = [FLASH_LOAN_MODE_NO_DEBT, FLASH_LOAN_MODE_NO_DEBT];
console.log('Executing multi-asset flash loan:');
tokens.forEach((token, i) => {
console.log(` ${amounts[i]} ${token.symbol}`);
});
console.log(`Pool: ${poolAddress}`);
console.log(`Receiver: ${FLASH_LOAN_RECEIVER}`);
if (!FLASH_LOAN_RECEIVER) {
throw new Error('FLASH_LOAN_RECEIVER environment variable not set');
}
// Execute multi-asset flash loan
// The receiver contract must:
// 1. Receive all loaned tokens
// 2. Perform desired operations (e.g., arbitrage)
// 3. For each asset, approve the pool for (amount + premium)
// 4. If mode = 2, approve for amount only (premium added to debt)
// 5. Return true from executeOperation
const tx = await walletClient.writeContract({
address: poolAddress,
abi: POOL_ABI,
functionName: 'flashLoan',
args: [
FLASH_LOAN_RECEIVER,
assets,
amounts,
modes,
account, // onBehalfOf
'0x' as Hex, // Optional params
0, // Referral code
],
});
await waitForTransaction(publicClient, tx);
console.log(`Multi-asset flash loan executed: ${tx}`);
console.log('\n✅ Multi-asset flash loan completed successfully!');
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
flashLoanMulti().catch(console.error);
}
export { flashLoanMulti };

View File

@@ -0,0 +1,104 @@
/**
* Aave v3: Single-asset flash loan
*
* This example demonstrates how to execute a flash loan for a single asset.
* Flash loans must be repaid within the same transaction, including a premium.
*/
import { createWalletRpcClient } from '../../src/utils/chain-config.js';
import { getAavePoolAddress } from '../../src/utils/addresses.js';
import { getTokenMetadata, parseTokenAmount } from '../../src/utils/tokens.js';
import { waitForTransaction } from '../../src/utils/rpc.js';
import type { Address, Hex } from 'viem';
const CHAIN_ID = 1; // Mainnet
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
// Aave Pool ABI
const POOL_ABI = [
{
name: 'flashLoanSimple',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'receiverAddress', type: 'address' },
{ name: 'asset', type: 'address' },
{ name: 'amount', type: 'uint256' },
{ name: 'params', type: 'bytes' },
{ name: 'referralCode', type: 'uint16' },
],
outputs: [],
},
] as const;
// Flash loan receiver contract ABI (you need to deploy this)
interface IFlashLoanReceiver {
executeOperation: (
asset: Address,
amount: bigint,
premium: bigint,
initiator: Address,
params: Hex
) => Promise<boolean>;
}
/**
* Example flash loan receiver contract address
*
* In production, you would deploy your own flash loan receiver contract
* that implements IFlashLoanReceiver and performs your desired logic.
*/
const FLASH_LOAN_RECEIVER = process.env.FLASH_LOAN_RECEIVER as `0x${string}`;
async function flashLoanSimple() {
const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY);
const publicClient = walletClient as any;
const account = walletClient.account?.address;
if (!account) {
throw new Error('No account available');
}
const poolAddress = getAavePoolAddress(CHAIN_ID);
const token = getTokenMetadata(CHAIN_ID, 'USDC');
const amount = parseTokenAmount('10000', token.decimals); // 10,000 USDC
console.log(`Executing flash loan for ${amount} ${token.symbol}`);
console.log(`Pool: ${poolAddress}`);
console.log(`Receiver: ${FLASH_LOAN_RECEIVER}`);
if (!FLASH_LOAN_RECEIVER) {
throw new Error('FLASH_LOAN_RECEIVER environment variable not set');
}
// Execute flash loan
// The receiver contract must:
// 1. Receive the loaned tokens
// 2. Perform desired operations
// 3. Approve the pool for (amount + premium)
// 4. Return true from executeOperation
const tx = await walletClient.writeContract({
address: poolAddress,
abi: POOL_ABI,
functionName: 'flashLoanSimple',
args: [
FLASH_LOAN_RECEIVER, // Your flash loan receiver contract
token.address,
amount,
'0x' as Hex, // Optional params
0, // Referral code
],
});
await waitForTransaction(publicClient, tx);
console.log(`Flash loan executed: ${tx}`);
console.log('\n✅ Flash loan completed successfully!');
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
flashLoanSimple().catch(console.error);
}
export { flashLoanSimple };

View File

@@ -0,0 +1,96 @@
/**
* Aave v3: Pool discovery using PoolAddressesProvider
*
* This example demonstrates how to discover the Aave Pool address
* using the PoolAddressesProvider service discovery pattern.
* This is the recommended way to get the Pool address in production.
*/
import { createRpcClient } from '../../src/utils/chain-config.js';
import { getAavePoolAddressesProvider } from '../../src/utils/addresses.js';
const CHAIN_ID = 1; // Mainnet
// PoolAddressesProvider ABI
const ADDRESSES_PROVIDER_ABI = [
{
name: 'getPool',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: '', type: 'address' }],
},
{
name: 'getPoolDataProvider',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: '', type: 'address' }],
},
{
name: 'getPriceOracle',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: '', type: 'address' }],
},
] as const;
async function discoverPool() {
const publicClient = createRpcClient(CHAIN_ID);
const addressesProvider = getAavePoolAddressesProvider(CHAIN_ID);
console.log('Discovering Aave v3 Pool addresses...');
console.log(`Chain ID: ${CHAIN_ID}`);
console.log(`PoolAddressesProvider: ${addressesProvider}\n`);
// Get Pool address
const poolAddress = await publicClient.readContract({
address: addressesProvider,
abi: ADDRESSES_PROVIDER_ABI,
functionName: 'getPool',
});
console.log(`✅ Pool: ${poolAddress}`);
// Get PoolDataProvider address (for querying reserves, user data, etc.)
try {
const dataProviderAddress = await publicClient.readContract({
address: addressesProvider,
abi: ADDRESSES_PROVIDER_ABI,
functionName: 'getPoolDataProvider',
});
console.log(`✅ PoolDataProvider: ${dataProviderAddress}`);
} catch (error) {
console.log('⚠️ PoolDataProvider not available (may be using different method)');
}
// Get PriceOracle address
try {
const priceOracleAddress = await publicClient.readContract({
address: addressesProvider,
abi: ADDRESSES_PROVIDER_ABI,
functionName: 'getPriceOracle',
});
console.log(`✅ PriceOracle: ${priceOracleAddress}`);
} catch (error) {
console.log('⚠️ PriceOracle not available (may be using different method)');
}
console.log('\n✅ Pool discovery completed!');
console.log('\nUse the Pool address for all Aave v3 operations:');
console.log(` - supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)`);
console.log(` - withdraw(address asset, uint256 amount, address to)`);
console.log(` - borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf)`);
console.log(` - repay(address asset, uint256 amount, uint256 rateMode, address onBehalfOf)`);
console.log(` - flashLoanSimple(address receiverAddress, address asset, uint256 amount, bytes params, uint16 referralCode)`);
console.log(` - flashLoan(address receiverAddress, address[] assets, uint256[] amounts, uint256[] modes, address onBehalfOf, bytes params, uint16 referralCode)`);
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
discoverPool().catch(console.error);
}
export { discoverPool };

View File

@@ -0,0 +1,161 @@
/**
* Aave v3: Supply collateral, enable as collateral, and borrow
*
* This example demonstrates:
* 1. Supplying assets to Aave v3
* 2. Enabling supplied asset as collateral
* 3. Borrowing against collateral
*
* Note: In Aave v3.3+, stable-rate borrowing has been deprecated. Use variable rate (mode = 2).
*/
import { createWalletRpcClient } from '../../src/utils/chain-config.js';
import { getAavePoolAddress } from '../../src/utils/addresses.js';
import { getTokenMetadata, parseTokenAmount } from '../../src/utils/tokens.js';
import { waitForTransaction } from '../../src/utils/rpc.js';
import { parseUnits } from 'viem';
const CHAIN_ID = 1; // Mainnet (change to 8453 for Base, 42161 for Arbitrum, etc.)
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
// ABI for Aave Pool
const POOL_ABI = [
{
name: 'supply',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'asset', type: 'address' },
{ name: 'amount', type: 'uint256' },
{ name: 'onBehalfOf', type: 'address' },
{ name: 'referralCode', type: 'uint16' },
],
outputs: [],
},
{
name: 'setUserUseReserveAsCollateral',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'asset', type: 'address' },
{ name: 'useAsCollateral', type: 'bool' },
],
outputs: [],
},
{
name: 'borrow',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'asset', type: 'address' },
{ name: 'amount', type: 'uint256' },
{ name: 'interestRateMode', type: 'uint256' },
{ name: 'referralCode', type: 'uint16' },
{ name: 'onBehalfOf', type: 'address' },
],
outputs: [],
},
] as const;
// ERC20 ABI for approvals
const ERC20_ABI = [
{
name: 'approve',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [{ name: '', type: 'bool' }],
},
{
name: 'allowance',
type: 'function',
stateMutability: 'view',
inputs: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
],
outputs: [{ name: '', type: 'uint256' }],
},
] as const;
async function supplyAndBorrow() {
const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY);
const publicClient = walletClient as any;
const account = walletClient.account?.address;
if (!account) {
throw new Error('No account available');
}
const poolAddress = getAavePoolAddress(CHAIN_ID);
// Token configuration
const collateralToken = getTokenMetadata(CHAIN_ID, 'USDC');
const debtToken = getTokenMetadata(CHAIN_ID, 'USDT');
// Amounts
const supplyAmount = parseTokenAmount('1000', collateralToken.decimals);
const borrowAmount = parseTokenAmount('500', debtToken.decimals);
console.log(`Supplying ${supplyAmount} ${collateralToken.symbol}`);
console.log(`Borrowing ${borrowAmount} ${debtToken.symbol}`);
console.log(`Pool: ${poolAddress}`);
console.log(`Account: ${account}`);
// Step 1: Approve token spending
console.log('\n1. Approving token spending...');
const approveTx = await walletClient.writeContract({
address: collateralToken.address,
abi: ERC20_ABI,
functionName: 'approve',
args: [poolAddress, supplyAmount],
});
await waitForTransaction(publicClient, approveTx);
console.log(`Approved: ${approveTx}`);
// Step 2: Supply collateral
console.log('\n2. Supplying collateral...');
const supplyTx = await walletClient.writeContract({
address: poolAddress,
abi: POOL_ABI,
functionName: 'supply',
args: [collateralToken.address, supplyAmount, account, 0],
});
await waitForTransaction(publicClient, supplyTx);
console.log(`Supplied: ${supplyTx}`);
// Step 3: Enable as collateral
console.log('\n3. Enabling as collateral...');
const enableCollateralTx = await walletClient.writeContract({
address: poolAddress,
abi: POOL_ABI,
functionName: 'setUserUseReserveAsCollateral',
args: [collateralToken.address, true],
});
await waitForTransaction(publicClient, enableCollateralTx);
console.log(`Enabled collateral: ${enableCollateralTx}`);
// Step 4: Borrow (variable rate = 2, stable rate is deprecated)
console.log('\n4. Borrowing...');
const borrowTx = await walletClient.writeContract({
address: poolAddress,
abi: POOL_ABI,
functionName: 'borrow',
args: [debtToken.address, borrowAmount, 2, 0, account], // mode 2 = variable
});
await waitForTransaction(publicClient, borrowTx);
console.log(`Borrowed: ${borrowTx}`);
console.log('\n✅ Supply and borrow completed successfully!');
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
supplyAndBorrow().catch(console.error);
}
export { supplyAndBorrow };

View File

@@ -0,0 +1,176 @@
/**
* Compound III: Supply collateral and borrow base asset
*
* This example demonstrates how to:
* 1. Supply collateral to Compound III
* 2. Borrow the base asset (e.g., USDC)
*
* Note: In Compound III, you "borrow" by withdrawing the base asset
* after supplying collateral. There's one base asset per market.
*/
import { createWalletRpcClient } from '../../src/utils/chain-config.js';
import { getCompound3Comet } from '../../src/utils/addresses.js';
import { getTokenMetadata, parseTokenAmount } from '../../src/utils/tokens.js';
import { waitForTransaction } from '../../src/utils/rpc.js';
const CHAIN_ID = 1; // Mainnet
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
// Compound III Comet ABI
const COMET_ABI = [
{
name: 'supply',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'asset', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [],
},
{
name: 'withdraw',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'asset', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [],
},
{
name: 'baseToken',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: '', type: 'address' }],
},
{
name: 'getBorrowBalance',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ name: '', type: 'uint256' }],
},
{
name: 'getCollateralBalance',
type: 'function',
stateMutability: 'view',
inputs: [
{ name: 'account', type: 'address' },
{ name: 'asset', type: 'address' },
],
outputs: [{ name: '', type: 'uint256' }],
},
] as const;
// ERC20 ABI for approvals
const ERC20_ABI = [
{
name: 'approve',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [{ name: '', type: 'bool' }],
},
] as const;
async function supplyAndBorrow() {
const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY);
const publicClient = walletClient as any;
const account = walletClient.account?.address;
if (!account) {
throw new Error('No account available');
}
const cometAddress = getCompound3Comet(CHAIN_ID);
// Get base token (USDC for USDC market)
console.log('Querying Comet contract...');
const baseToken = await publicClient.readContract({
address: cometAddress,
abi: COMET_ABI,
functionName: 'baseToken',
}) as `0x${string}`;
console.log(`Comet: ${cometAddress}`);
console.log(`Base token: ${baseToken}`);
// Use WETH as collateral (adjust based on market)
const collateralToken = getTokenMetadata(CHAIN_ID, 'WETH');
const baseTokenMetadata = getTokenMetadata(CHAIN_ID, 'USDC'); // Assuming USDC market
const collateralAmount = parseTokenAmount('1', collateralToken.decimals); // 1 WETH
const borrowAmount = parseTokenAmount('2000', baseTokenMetadata.decimals); // 2000 USDC
console.log(`\nSupplying ${collateralAmount} ${collateralToken.symbol} as collateral`);
console.log(`Borrowing ${borrowAmount} ${baseTokenMetadata.symbol} (base asset)`);
// Step 1: Approve collateral token
console.log('\n1. Approving collateral token...');
const approveTx = await walletClient.writeContract({
address: collateralToken.address,
abi: ERC20_ABI,
functionName: 'approve',
args: [cometAddress, collateralAmount],
});
await waitForTransaction(publicClient, approveTx);
console.log(`Approved: ${approveTx}`);
// Step 2: Supply collateral
console.log('\n2. Supplying collateral...');
const supplyTx = await walletClient.writeContract({
address: cometAddress,
abi: COMET_ABI,
functionName: 'supply',
args: [collateralToken.address, collateralAmount],
});
await waitForTransaction(publicClient, supplyTx);
console.log(`Supplied: ${supplyTx}`);
// Step 3: "Borrow" by withdrawing base asset
// In Compound III, borrowing is done by withdrawing the base asset
console.log('\n3. Borrowing base asset (withdrawing)...');
const borrowTx = await walletClient.writeContract({
address: cometAddress,
abi: COMET_ABI,
functionName: 'withdraw',
args: [baseToken, borrowAmount],
});
await waitForTransaction(publicClient, borrowTx);
console.log(`Borrowed: ${borrowTx}`);
// Step 4: Check balances
console.log('\n4. Checking positions...');
const borrowBalance = await publicClient.readContract({
address: cometAddress,
abi: COMET_ABI,
functionName: 'getBorrowBalance',
args: [account],
}) as bigint;
const collateralBalance = await publicClient.readContract({
address: cometAddress,
abi: COMET_ABI,
functionName: 'getCollateralBalance',
args: [account, collateralToken.address],
}) as bigint;
console.log(`Borrow balance: ${borrowBalance} ${baseTokenMetadata.symbol}`);
console.log(`Collateral balance: ${collateralBalance} ${collateralToken.symbol}`);
console.log('\n✅ Supply and borrow completed successfully!');
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
supplyAndBorrow().catch(console.error);
}
export { supplyAndBorrow };

View File

@@ -0,0 +1,82 @@
/**
* Cross-Protocol: Flash loan arbitrage pattern
*
* This example demonstrates a flash loan arbitrage strategy:
* 1. Flash loan USDC from Aave
* 2. Swap USDC → DAI on Uniswap v3
* 3. Swap DAI → USDC on another DEX (or different pool)
* 4. Repay flash loan with premium
* 5. Keep profit
*
* Note: This is a conceptual example. Real arbitrage requires:
* - Price difference detection
* - Gas cost calculation
* - Slippage protection
* - MEV protection
*/
import { createWalletRpcClient } from '../../src/utils/chain-config.js';
import { getAavePoolAddress, getUniswapSwapRouter02 } from '../../src/utils/addresses.js';
import { getTokenMetadata, parseTokenAmount } from '../../src/utils/tokens.js';
import { waitForTransaction } from '../../src/utils/rpc.js';
import type { Address } from 'viem';
const CHAIN_ID = 1; // Mainnet
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
/**
* Flash loan receiver contract for arbitrage
*
* In production, you would deploy a contract that:
* 1. Receives flash loan from Aave
* 2. Executes arbitrage swaps
* 3. Repays flash loan
* 4. Sends profit to owner
*
* See contracts/examples/AaveFlashLoanReceiver.sol for Solidity implementation
*/
const ARBITRAGE_CONTRACT = process.env.ARBITRAGE_CONTRACT as `0x${string}`;
async function flashLoanArbitrage() {
const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY);
const publicClient = walletClient as any;
const account = walletClient.account?.address;
if (!account) {
throw new Error('No account available');
}
const poolAddress = getAavePoolAddress(CHAIN_ID);
const token = getTokenMetadata(CHAIN_ID, 'USDC');
const amount = parseTokenAmount('100000', token.decimals); // 100,000 USDC
console.log('Flash loan arbitrage strategy:');
console.log(` 1. Flash loan ${amount} ${token.symbol} from Aave`);
console.log(` 2. Execute arbitrage swaps`);
console.log(` 3. Repay flash loan`);
console.log(` 4. Keep profit`);
console.log(`\nArbitrage contract: ${ARBITRAGE_CONTRACT}`);
if (!ARBITRAGE_CONTRACT) {
throw new Error('ARBITRAGE_CONTRACT environment variable not set');
}
// Note: In production, this would be done through a smart contract
// that implements IFlashLoanReceiver and executes the arbitrage logic
console.log('\n⚠ This is a conceptual example.');
console.log('In production:');
console.log(' 1. Deploy a flash loan receiver contract');
console.log(' 2. Contract receives flash loan');
console.log(' 3. Contract executes arbitrage (swaps)');
console.log(' 4. Contract repays flash loan + premium');
console.log(' 5. Contract sends profit to owner');
console.log('\nSee contracts/examples/AaveFlashLoanReceiver.sol for implementation');
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
flashLoanArbitrage().catch(console.error);
}
export { flashLoanArbitrage };

View File

@@ -0,0 +1,135 @@
/**
* Protocolink: Complex multi-step batch transactions
*
* This example demonstrates how to build complex multi-step transactions
* using Protocolink, such as:
* - Flash loan
* - Swap
* - Supply
* - Borrow
* - Repay
* All in one transaction!
*/
import * as api from '@protocolink/api';
import * as common from '@protocolink/common';
import { createWalletRpcClient } from '../../src/utils/chain-config.js';
import { waitForTransaction } from '../../src/utils/rpc.js';
const CHAIN_ID = common.ChainId.mainnet;
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
async function batchComplexTransaction() {
const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY);
const publicClient = walletClient as any;
const account = walletClient.account?.address;
if (!account) {
throw new Error('No account available');
}
const USDC: common.Token = {
chainId: CHAIN_ID,
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
decimals: 6,
symbol: 'USDC',
name: 'USD Coin',
};
const USDT: common.Token = {
chainId: CHAIN_ID,
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
decimals: 6,
symbol: 'USDT',
name: 'Tether USD',
};
console.log('Building complex batch transaction:');
console.log(' 1. Flash loan USDC');
console.log(' 2. Supply USDC to Aave');
console.log(' 3. Borrow USDT from Aave');
console.log(' 4. Swap USDT → USDC');
console.log(' 5. Repay flash loan');
console.log(`Account: ${account}`);
try {
const logics: any[] = [];
const flashLoanAmount = '10000'; // 10,000 USDC
// Step 1: Flash loan logic (using utility flash loan)
console.log('\n1. Adding flash loan logic...');
const flashLoanQuotation = await api.utility.getFlashLoanQuotation(CHAIN_ID, {
loans: [{ token: USDC, amount: flashLoanAmount }],
});
const flashLoanLogic = api.utility.newFlashLoanLogic(flashLoanQuotation);
logics.push(flashLoanLogic);
// Step 2: Supply logic
console.log('2. Adding supply logic...');
const supplyQuotation = await api.protocols.aavev3.getSupplyQuotation(CHAIN_ID, {
input: { token: USDC, amount: flashLoanAmount },
});
const supplyLogic = api.protocols.aavev3.newSupplyLogic(supplyQuotation);
logics.push(supplyLogic);
// Step 3: Borrow logic
console.log('3. Adding borrow logic...');
const borrowAmount = '5000'; // 5,000 USDT
const borrowQuotation = await api.protocols.aavev3.getBorrowQuotation(CHAIN_ID, {
output: { token: USDT, amount: borrowAmount },
});
const borrowLogic = api.protocols.aavev3.newBorrowLogic(borrowQuotation);
logics.push(borrowLogic);
// Step 4: Swap logic
console.log('4. Adding swap logic...');
const swapQuotation = await api.protocols.uniswapv3.getSwapTokenQuotation(CHAIN_ID, {
input: { token: USDT, amount: borrowAmount },
tokenOut: USDC,
slippage: 100, // 1% slippage
});
const swapLogic = api.protocols.uniswapv3.newSwapTokenLogic(swapQuotation);
logics.push(swapLogic);
// Step 5: Flash loan repay logic
console.log('5. Adding flash loan repay logic...');
const flashLoanRepayLogic = api.utility.newFlashLoanRepayLogic({
id: flashLoanLogic.id,
input: swapQuotation.output, // Use swapped USDC to repay
});
logics.push(flashLoanRepayLogic);
// Step 6: Get router data and execute
console.log('\n6. Building router transaction...');
const routerData = await api.router.getRouterData(CHAIN_ID, {
account,
logics,
});
console.log(`Router: ${routerData.router}`);
console.log(`Estimated gas: ${routerData.estimation.gas}`);
console.log('\n7. Executing transaction...');
const tx = await walletClient.sendTransaction({
to: routerData.router,
data: routerData.data,
value: BigInt(routerData.estimation.value || '0'),
gas: BigInt(routerData.estimation.gas),
});
await waitForTransaction(publicClient, tx);
console.log(`Transaction executed: ${tx}`);
console.log('\n✅ Complex batch transaction completed successfully!');
} catch (error) {
console.error('Error executing batch transaction:', error);
throw error;
}
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
batchComplexTransaction().catch(console.error);
}
export { batchComplexTransaction };

View File

@@ -0,0 +1,114 @@
/**
* Protocolink: Multi-protocol composition (swap → supply)
*
* This example demonstrates how to compose multiple DeFi operations
* into a single transaction using Protocolink.
*
* Example: Swap USDC → WBTC on Uniswap v3, then supply WBTC to Aave v3
*/
import * as api from '@protocolink/api';
import * as common from '@protocolink/common';
import { createWalletRpcClient } from '../../src/utils/chain-config.js';
import { waitForTransaction } from '../../src/utils/rpc.js';
const CHAIN_ID = common.ChainId.mainnet; // 1 for mainnet, 8453 for Base, etc.
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
async function composeSwapAndSupply() {
const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY);
const publicClient = walletClient as any;
const account = walletClient.account?.address;
if (!account) {
throw new Error('No account available');
}
// Token definitions
const USDC: common.Token = {
chainId: CHAIN_ID,
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
decimals: 6,
symbol: 'USDC',
name: 'USD Coin',
};
const WBTC: common.Token = {
chainId: CHAIN_ID,
address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
decimals: 8,
symbol: 'WBTC',
name: 'Wrapped Bitcoin',
};
const amountIn = '1000'; // 1000 USDC
const slippage = 100; // 1% slippage tolerance in basis points
console.log(`Composing transaction: Swap ${amountIn} USDC → WBTC, then supply to Aave`);
console.log(`Chain ID: ${CHAIN_ID}`);
console.log(`Account: ${account}`);
try {
// Step 1: Get swap quotation from Uniswap v3
console.log('\n1. Getting swap quotation...');
const swapQuotation = await api.protocols.uniswapv3.getSwapTokenQuotation(CHAIN_ID, {
input: { token: USDC, amount: amountIn },
tokenOut: WBTC,
slippage,
});
console.log(`Expected output: ${swapQuotation.output.amount} ${swapQuotation.output.token.symbol}`);
// Step 2: Build swap logic
const swapLogic = api.protocols.uniswapv3.newSwapTokenLogic(swapQuotation);
// Step 3: Get Aave v3 supply quotation
console.log('\n2. Getting Aave supply quotation...');
const supplyQuotation = await api.protocols.aavev3.getSupplyQuotation(CHAIN_ID, {
input: swapQuotation.output, // Use WBTC from swap as input
tokenOut: swapQuotation.output.token, // aWBTC (Protocolink will resolve the aToken)
});
console.log(`Expected aToken output: ${supplyQuotation.output.amount} ${supplyQuotation.output.token.symbol}`);
// Step 4: Build supply logic
const supplyLogic = api.protocols.aavev3.newSupplyLogic(supplyQuotation);
// Step 5: Build router logics (combine swap + supply)
const routerLogics = [swapLogic, supplyLogic];
// Step 6: Get router data
console.log('\n3. Building router transaction...');
const routerData = await api.router.getRouterData(CHAIN_ID, {
account,
logics: routerLogics,
});
console.log(`Router: ${routerData.router}`);
console.log(`Estimated gas: ${routerData.estimation.gas}`);
// Step 7: Execute transaction
console.log('\n4. Executing transaction...');
const tx = await walletClient.sendTransaction({
to: routerData.router,
data: routerData.data,
value: BigInt(routerData.estimation.value || '0'),
gas: BigInt(routerData.estimation.gas),
});
await waitForTransaction(publicClient, tx);
console.log(`Transaction executed: ${tx}`);
console.log('\n✅ Multi-protocol transaction completed successfully!');
} catch (error) {
console.error('Error composing transaction:', error);
throw error;
}
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
composeSwapAndSupply().catch(console.error);
}
export { composeSwapAndSupply };

View File

@@ -0,0 +1,116 @@
/**
* Protocolink: Protocolink with Permit2 signatures
*
* This example demonstrates how to use Protocolink with Permit2
* for gasless approvals via signatures.
*/
import * as api from '@protocolink/api';
import * as common from '@protocolink/common';
import { createWalletRpcClient } from '../../src/utils/chain-config.js';
import { waitForTransaction } from '../../src/utils/rpc.js';
const CHAIN_ID = common.ChainId.mainnet;
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
async function protocolinkWithPermit2() {
const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY);
const publicClient = walletClient as any;
const account = walletClient.account?.address;
if (!account) {
throw new Error('No account available');
}
const USDC: common.Token = {
chainId: CHAIN_ID,
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
decimals: 6,
symbol: 'USDC',
name: 'USD Coin',
};
const WBTC: common.Token = {
chainId: CHAIN_ID,
address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
decimals: 8,
symbol: 'WBTC',
name: 'Wrapped Bitcoin',
};
const amountIn = '1000'; // 1000 USDC
console.log(`Using Protocolink with Permit2 for gasless approvals`);
console.log(`Swapping ${amountIn} USDC → WBTC, then supplying to Aave`);
console.log(`Account: ${account}`);
try {
// Step 1: Get swap quotation
const swapQuotation = await api.protocols.uniswapv3.getSwapTokenQuotation(CHAIN_ID, {
input: { token: USDC, amount: amountIn },
tokenOut: WBTC,
slippage: 100,
});
// Step 2: Build swap logic
const swapLogic = api.protocols.uniswapv3.newSwapTokenLogic(swapQuotation);
// Step 3: Get supply quotation
const supplyQuotation = await api.protocols.aavev3.getSupplyQuotation(CHAIN_ID, {
input: swapQuotation.output,
});
// Step 4: Build supply logic
const supplyLogic = api.protocols.aavev3.newSupplyLogic(supplyQuotation);
const routerLogics = [swapLogic, supplyLogic];
// Step 5: Get permit2 data (if token supports it)
// Protocolink will automatically use Permit2 when available
console.log('\n1. Building router transaction with Permit2...');
const routerData = await api.router.getRouterData(CHAIN_ID, {
account,
logics: routerLogics,
// Permit2 will be used automatically if:
// 1. Token supports Permit2
// 2. User has sufficient balance
// 3. No existing approval
});
console.log(`Router: ${routerData.router}`);
console.log(`Using Permit2: ${routerData.permit2Data ? 'Yes' : 'No'}`);
console.log(`Estimated gas: ${routerData.estimation.gas}`);
// Step 6: If Permit2 data is provided, sign it
if (routerData.permit2Data) {
console.log('\n2. Signing Permit2 permit...');
// Protocolink SDK handles Permit2 signing internally
// You may need to sign the permit data before executing
// See Protocolink docs for exact flow
}
// Step 7: Execute transaction
console.log('\n3. Executing transaction...');
const tx = await walletClient.sendTransaction({
to: routerData.router,
data: routerData.data,
value: BigInt(routerData.estimation.value || '0'),
gas: BigInt(routerData.estimation.gas),
});
await waitForTransaction(publicClient, tx);
console.log(`Transaction executed: ${tx}`);
console.log('\n✅ Transaction with Permit2 completed successfully!');
} catch (error) {
console.error('Error executing transaction:', error);
throw error;
}
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
protocolinkWithPermit2().catch(console.error);
}
export { protocolinkWithPermit2 };

View File

@@ -0,0 +1,132 @@
/**
* Cross-Protocol: Complete DeFi strategy example
*
* This example demonstrates a complete DeFi strategy using Protocolink:
* 1. Supply USDC to Aave v3
* 2. Enable as collateral
* 3. Borrow USDT from Aave v3
* 4. Swap USDT → USDC on Uniswap v3
* 5. Supply swapped USDC back to Aave
*
* All in one transaction via Protocolink!
*/
import * as api from '@protocolink/api';
import * as common from '@protocolink/common';
import { createWalletRpcClient } from '../../src/utils/chain-config.js';
import { waitForTransaction } from '../../src/utils/rpc.js';
const CHAIN_ID = common.ChainId.mainnet;
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
async function supplyBorrowSwapStrategy() {
const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY);
const publicClient = walletClient as any;
const account = walletClient.account?.address;
if (!account) {
throw new Error('No account available');
}
const USDC: common.Token = {
chainId: CHAIN_ID,
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
decimals: 6,
symbol: 'USDC',
name: 'USD Coin',
};
const USDT: common.Token = {
chainId: CHAIN_ID,
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
decimals: 6,
symbol: 'USDT',
name: 'Tether USD',
};
const initialSupply = '5000'; // 5,000 USDC
const borrowAmount = '2000'; // 2,000 USDT
console.log('Complete DeFi strategy:');
console.log(` 1. Supply ${initialSupply} USDC to Aave`);
console.log(` 2. Enable as collateral`);
console.log(` 3. Borrow ${borrowAmount} USDT from Aave`);
console.log(` 4. Swap USDT → USDC on Uniswap v3`);
console.log(` 5. Supply swapped USDC back to Aave`);
console.log(`\nAccount: ${account}`);
try {
const logics: any[] = [];
// Step 1: Supply USDC
console.log('\n1. Adding supply logic...');
const supplyQuotation1 = await api.protocols.aavev3.getSupplyQuotation(CHAIN_ID, {
input: { token: USDC, amount: initialSupply },
});
const supplyLogic1 = api.protocols.aavev3.newSupplyLogic(supplyQuotation1);
logics.push(supplyLogic1);
// Step 2: Set as collateral (may be automatic, check Aave docs)
// Note: Some Aave markets automatically enable as collateral
console.log('2. Collateral enabled automatically in most markets');
// Step 3: Borrow USDT
console.log('3. Adding borrow logic...');
const borrowQuotation = await api.protocols.aavev3.getBorrowQuotation(CHAIN_ID, {
output: { token: USDT, amount: borrowAmount },
});
const borrowLogic = api.protocols.aavev3.newBorrowLogic(borrowQuotation);
logics.push(borrowLogic);
// Step 4: Swap USDT → USDC
console.log('4. Adding swap logic...');
const swapQuotation = await api.protocols.uniswapv3.getSwapTokenQuotation(CHAIN_ID, {
input: { token: USDT, amount: borrowAmount },
tokenOut: USDC,
slippage: 100, // 1% slippage
});
const swapLogic = api.protocols.uniswapv3.newSwapTokenLogic(swapQuotation);
logics.push(swapLogic);
// Step 5: Supply swapped USDC
console.log('5. Adding second supply logic...');
const supplyQuotation2 = await api.protocols.aavev3.getSupplyQuotation(CHAIN_ID, {
input: swapQuotation.output, // Use swapped USDC
});
const supplyLogic2 = api.protocols.aavev3.newSupplyLogic(supplyQuotation2);
logics.push(supplyLogic2);
// Step 6: Execute all in one transaction
console.log('\n6. Building router transaction...');
const routerData = await api.router.getRouterData(CHAIN_ID, {
account,
logics,
});
console.log(`Router: ${routerData.router}`);
console.log(`Estimated gas: ${routerData.estimation.gas}`);
console.log('\n7. Executing transaction...');
const tx = await walletClient.sendTransaction({
to: routerData.router,
data: routerData.data,
value: BigInt(routerData.estimation.value || '0'),
gas: BigInt(routerData.estimation.gas),
});
await waitForTransaction(publicClient, tx);
console.log(`Transaction executed: ${tx}`);
console.log('\n✅ Complete DeFi strategy executed successfully!');
} catch (error) {
console.error('Error executing strategy:', error);
throw error;
}
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
supplyBorrowSwapStrategy().catch(console.error);
}
export { supplyBorrowSwapStrategy };

View File

@@ -0,0 +1,131 @@
/**
* Uniswap: Permit2 signature-based approvals
*
* This example demonstrates how to use Permit2 for signature-based token approvals.
* Permit2 allows users to approve tokens via signatures instead of on-chain transactions.
*/
import { createWalletRpcClient } from '../../src/utils/chain-config.js';
import { getPermit2Address, getUniswapSwapRouter02 } from '../../src/utils/addresses.js';
import { getTokenMetadata, parseTokenAmount } from '../../src/utils/tokens.js';
import { getPermit2Domain, getPermit2TransferTypes, createPermit2Deadline } from '../../src/utils/permit2.js';
import { waitForTransaction } from '../../src/utils/rpc.js';
import { signTypedData } from 'viem';
import type { Address } from 'viem';
const CHAIN_ID = 1; // Mainnet
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
// Permit2 ABI for permit transfer
const PERMIT2_ABI = [
{
name: 'permitTransferFrom',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{
components: [
{
components: [
{ name: 'token', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
name: 'permitted',
type: 'tuple',
},
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
name: 'permit',
type: 'tuple',
},
{
components: [
{ name: 'to', type: 'address' },
{ name: 'requestedAmount', type: 'uint256' },
],
name: 'transferDetails',
type: 'tuple',
},
{ name: 'signature', type: 'bytes' },
],
outputs: [],
},
] as const;
async function permit2Approval() {
const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY);
const publicClient = walletClient as any;
const account = walletClient.account?.address;
if (!account) {
throw new Error('No account available');
}
const permit2Address = getPermit2Address(CHAIN_ID);
const token = getTokenMetadata(CHAIN_ID, 'USDC');
const amount = parseTokenAmount('1000', token.decimals);
const spender = getUniswapSwapRouter02(CHAIN_ID); // Example: approve Uniswap router
console.log(`Creating Permit2 signature for ${amount} ${token.symbol}`);
console.log(`Permit2: ${permit2Address}`);
console.log(`Spender: ${spender}`);
console.log(`Account: ${account}`);
// Step 1: Get nonce from Permit2
// In production, query the Permit2 contract for the user's current nonce
const nonce = 0n; // TODO: Read from Permit2 contract
// Step 2: Create permit data
const deadline = createPermit2Deadline(3600); // 1 hour
const domain = getPermit2Domain(CHAIN_ID);
const types = getPermit2TransferTypes();
const permit = {
permitted: {
token: token.address,
amount,
},
nonce,
deadline,
};
const transferDetails = {
to: spender,
requestedAmount: amount,
};
// Step 3: Sign the permit
console.log('\n1. Signing Permit2 permit...');
const signature = await signTypedData(walletClient, {
domain,
types,
primaryType: 'PermitTransferFrom',
message: {
permitted: permit.permitted,
spender,
nonce: permit.nonce,
deadline: permit.deadline,
},
});
console.log(`Signature: ${signature}`);
// Step 4: Execute permitTransferFrom (this would typically be done by a router/contract)
// Note: In practice, Permit2 permits are usually used within larger transaction flows
// (e.g., Universal Router uses them automatically)
console.log('\n2. Permit2 signature created successfully!');
console.log('Use this signature in your transaction (e.g., Universal Router)');
console.log('\nExample usage with Universal Router:');
console.log(' - Universal Router will call permitTransferFrom on Permit2');
console.log(' - Then execute the swap/transfer');
console.log(' - All in one transaction');
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
permit2Approval().catch(console.error);
}
export { permit2Approval };

View File

@@ -0,0 +1,136 @@
/**
* Uniswap: Universal Router with Permit2 integration
*
* This example demonstrates how to use Universal Router for complex multi-step transactions
* with Permit2 signature-based approvals.
*
* Universal Router supports:
* - Token swaps (Uniswap v2/v3)
* - NFT operations
* - Permit2 approvals
* - WETH wrapping/unwrapping
* - Multiple commands in one transaction
*/
import { createWalletRpcClient } from '../../src/utils/chain-config.js';
import { getUniswapUniversalRouter, getPermit2Address } from '../../src/utils/addresses.js';
import { getTokenMetadata, parseTokenAmount } from '../../src/utils/tokens.js';
import { waitForTransaction } from '../../src/utils/rpc.js';
import type { Address, Hex } from 'viem';
const CHAIN_ID = 1; // Mainnet (change to 8453 for Base, etc.)
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
// Universal Router ABI
const UNIVERSAL_ROUTER_ABI = [
{
name: 'execute',
type: 'function',
stateMutability: 'payable',
inputs: [
{ name: 'commands', type: 'bytes' },
{ name: 'inputs', type: 'bytes[]' },
],
outputs: [],
},
{
name: 'execute',
type: 'function',
stateMutability: 'payable',
inputs: [
{ name: 'commands', type: 'bytes' },
{ name: 'inputs', type: 'bytes[]' },
{ name: 'deadline', type: 'uint256' },
],
outputs: [],
},
] as const;
/**
* Universal Router command types
* See: https://github.com/Uniswap/universal-router/blob/main/contracts/Commands.sol
*/
const COMMANDS = {
V3_SWAP_EXACT_IN: 0x00,
V3_SWAP_EXACT_OUT: 0x01,
PERMIT2_TRANSFER_FROM: 0x02,
PERMIT2_PERMIT_BATCH: 0x03,
SWEEP: 0x04,
TRANSFER: 0x05,
PAY_PORTION: 0x06,
V2_SWAP_EXACT_IN: 0x08,
V2_SWAP_EXACT_OUT: 0x09,
PERMIT2_PERMIT: 0x0a,
WRAP_ETH: 0x0b,
UNWRAP_WETH: 0x0c,
PERMIT2_TRANSFER_FROM_BATCH: 0x0d,
} as const;
/**
* Encode V3 swap exact input command
*
* This is a simplified example. In production, use the Universal Router SDK
* or carefully encode commands according to the Universal Router spec.
*/
function encodeV3SwapExactInput(params: {
recipient: Address;
amountIn: bigint;
amountOutMin: bigint;
path: Hex;
payerIsUser: boolean;
}): { command: number; input: Hex } {
// This is a conceptual example. Actual encoding is more complex.
// See: https://docs.uniswap.org/contracts/universal-router/technical-reference
throw new Error('Use Universal Router SDK for proper command encoding');
}
async function universalRouterSwap() {
const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY);
const publicClient = walletClient as any;
const account = walletClient.account?.address;
if (!account) {
throw new Error('No account available');
}
const routerAddress = getUniswapUniversalRouter(CHAIN_ID);
const tokenIn = getTokenMetadata(CHAIN_ID, 'USDC');
const tokenOut = getTokenMetadata(CHAIN_ID, 'WETH');
const amountIn = parseTokenAmount('1000', tokenIn.decimals);
const deadline = BigInt(Math.floor(Date.now() / 1000) + 600);
console.log(`Universal Router swap: ${amountIn} ${tokenIn.symbol} -> ${tokenOut.symbol}`);
console.log(`Router: ${routerAddress}`);
console.log(`Account: ${account}`);
// Note: Universal Router command encoding is complex.
// In production, use:
// 1. Universal Router SDK (when available)
// 2. Or carefully encode commands according to the spec
// 3. Or use Protocolink which handles Universal Router integration
console.log('\n⚠ This is a conceptual example.');
console.log('In production, use:');
console.log(' 1. Universal Router SDK for command encoding');
console.log(' 2. Or use Protocolink which integrates Universal Router');
console.log(' 3. Or carefully follow the Universal Router spec:');
console.log(' https://docs.uniswap.org/contracts/universal-router/technical-reference');
// Example flow:
// 1. Create Permit2 signature (see uniswap-permit2.ts)
// 2. Encode Universal Router commands
// 3. Execute via Universal Router.execute()
// 4. Universal Router will:
// - Use Permit2 to transfer tokens
// - Execute swap
// - Send output to recipient
// - All in one transaction
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
universalRouterSwap().catch(console.error);
}
export { universalRouterSwap, COMMANDS };

View File

@@ -0,0 +1,186 @@
/**
* Uniswap v3: TWAP Oracle usage
*
* This example demonstrates how to use Uniswap v3 pools as price oracles
* by querying time-weighted average prices (TWAP).
*
* Note: Always use TWAP, not spot prices, to protect against manipulation.
*/
import { createRpcClient } from '../../src/utils/chain-config.js';
import { getTokenMetadata } from '../../src/utils/tokens.js';
import type { Address } from 'viem';
const CHAIN_ID = 1; // Mainnet
// Uniswap v3 Pool ABI
const POOL_ABI = [
{
name: 'slot0',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [
{ name: 'sqrtPriceX96', type: 'uint160' },
{ name: 'tick', type: 'int24' },
{ name: 'observationIndex', type: 'uint16' },
{ name: 'observationCardinality', type: 'uint16' },
{ name: 'observationCardinalityNext', type: 'uint16' },
{ name: 'feeProtocol', type: 'uint8' },
{ name: 'unlocked', type: 'bool' },
],
},
{
name: 'observations',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'index', type: 'uint16' }],
outputs: [
{ name: 'blockTimestamp', type: 'uint32' },
{ name: 'tickCumulative', type: 'int56' },
{ name: 'secondsPerLiquidityCumulativeX128', type: 'uint160' },
{ name: 'initialized', type: 'bool' },
],
},
{
name: 'token0',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: '', type: 'address' }],
},
{
name: 'token1',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: '', type: 'address' }],
},
{
name: 'fee',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: '', type: 'uint24' }],
},
] as const;
/**
* Calculate price from sqrtPriceX96
* price = (sqrtPriceX96 / 2^96)^2
*/
function calculatePriceFromSqrtPriceX96(sqrtPriceX96: bigint): number {
const Q96 = 2n ** 96n;
const price = Number(sqrtPriceX96) ** 2 / Number(Q96) ** 2;
return price;
}
/**
* Calculate TWAP from observations
*
* TWAP = (tickCumulative1 - tickCumulative0) / (time1 - time0)
*/
function calculateTWAP(
tickCumulative0: bigint,
tickCumulative1: bigint,
time0: number,
time1: number
): number {
if (time1 === time0) {
throw new Error('Time difference cannot be zero');
}
const tickDelta = Number(tickCumulative1 - tickCumulative0);
const timeDelta = time1 - time0;
const avgTick = tickDelta / timeDelta;
// Convert tick to price: price = 1.0001^tick
const price = 1.0001 ** avgTick;
return price;
}
/**
* Get pool address from Uniswap v3 Factory
* In production, use the official Uniswap v3 SDK to compute pool addresses
*/
async function getPoolAddress(
client: any,
token0: Address,
token1: Address,
fee: number
): Promise<Address> {
// This is a simplified example. In production, use:
// 1. Uniswap v3 Factory to get pool address
// 2. Or compute pool address using CREATE2 (see Uniswap v3 SDK)
// For now, this is a placeholder
throw new Error('Implement pool address resolution using Factory or SDK');
}
async function queryOracle() {
const publicClient = createRpcClient(CHAIN_ID);
const token0 = getTokenMetadata(CHAIN_ID, 'USDC');
const token1 = getTokenMetadata(CHAIN_ID, 'WETH');
const fee = 3000; // 0.3% fee tier
console.log(`Querying Uniswap v3 TWAP oracle for ${token0.symbol}/${token1.symbol}`);
console.log(`Fee tier: ${fee} (0.3%)`);
// Note: In production, you need to:
// 1. Get the pool address from Uniswap v3 Factory
// 2. Or use the Uniswap v3 SDK to compute it
// For this example, we'll demonstrate the concept
// Example: Query current slot0 (spot price - not recommended for production!)
// const poolAddress = await getPoolAddress(publicClient, token0.address, token1.address, fee);
// const slot0 = await publicClient.readContract({
// address: poolAddress,
// abi: POOL_ABI,
// functionName: 'slot0',
// });
// const sqrtPriceX96 = slot0[0];
// const currentPrice = calculatePriceFromSqrtPriceX96(sqrtPriceX96);
// console.log(`Current spot price: ${currentPrice} ${token1.symbol} per ${token0.symbol}`);
// Example: Query TWAP from observations
// const observationIndex = slot0[2];
// const observation0 = await publicClient.readContract({
// address: poolAddress,
// abi: POOL_ABI,
// functionName: 'observations',
// args: [observationIndex],
// });
// Query a previous observation (e.g., 1 hour ago)
// const previousIndex = (observationIndex - 3600) % observationCardinality;
// const observation1 = await publicClient.readContract({
// address: poolAddress,
// abi: POOL_ABI,
// functionName: 'observations',
// args: [previousIndex],
// });
// const twap = calculateTWAP(
// observation0.tickCumulative,
// observation1.tickCumulative,
// observation0.blockTimestamp,
// observation1.blockTimestamp
// );
// console.log(`TWAP (1 hour): ${twap} ${token1.symbol} per ${token0.symbol}`);
console.log('\n⚠ This is a conceptual example.');
console.log('In production, use:');
console.log(' 1. Uniswap v3 OracleLibrary (see Uniswap v3 periphery contracts)');
console.log(' 2. Uniswap v3 SDK for price calculations');
console.log(' 3. Always use TWAP, never spot prices');
console.log(' 4. Ensure sufficient observation cardinality for your TWAP window');
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
queryOracle().catch(console.error);
}
export { queryOracle, calculatePriceFromSqrtPriceX96, calculateTWAP };

View File

@@ -0,0 +1,162 @@
/**
* Uniswap v3: Exact input swap via SwapRouter02
*
* This example demonstrates how to execute a swap on Uniswap v3
* using the SwapRouter02 contract.
*/
import { createWalletRpcClient } from '../../src/utils/chain-config.js';
import { getUniswapSwapRouter02 } from '../../src/utils/addresses.js';
import { getTokenMetadata, parseTokenAmount } from '../../src/utils/tokens.js';
import { waitForTransaction } from '../../src/utils/rpc.js';
import type { Address } from 'viem';
const CHAIN_ID = 1; // Mainnet (change to 8453 for Base, etc.)
const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}`;
// Uniswap v3 SwapRouter02 ABI
const SWAP_ROUTER_ABI = [
{
name: 'exactInputSingle',
type: 'function',
stateMutability: 'payable',
inputs: [
{
components: [
{ name: 'tokenIn', type: 'address' },
{ name: 'tokenOut', type: 'address' },
{ name: 'fee', type: 'uint24' },
{ name: 'recipient', type: 'address' },
{ name: 'deadline', type: 'uint256' },
{ name: 'amountIn', type: 'uint256' },
{ name: 'amountOutMinimum', type: 'uint256' },
{ name: 'sqrtPriceLimitX96', type: 'uint160' },
],
name: 'params',
type: 'tuple',
},
],
outputs: [{ name: 'amountOut', type: 'uint256' }],
},
{
name: 'exactInput',
type: 'function',
stateMutability: 'payable',
inputs: [
{
components: [
{
components: [
{ name: 'tokenIn', type: 'address' },
{ name: 'tokenOut', type: 'address' },
{ name: 'fee', type: 'uint24' },
],
name: 'path',
type: 'tuple[]',
},
{ name: 'recipient', type: 'address' },
{ name: 'deadline', type: 'uint256' },
{ name: 'amountIn', type: 'uint256' },
{ name: 'amountOutMinimum', type: 'uint256' },
],
name: 'params',
type: 'tuple',
},
],
outputs: [{ name: 'amountOut', type: 'uint256' }],
},
] as const;
// ERC20 ABI for approvals
const ERC20_ABI = [
{
name: 'approve',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [{ name: '', type: 'bool' }],
},
] as const;
// Uniswap v3 fee tiers (0.01%, 0.05%, 0.3%, 1%)
const FEE_TIER_LOW = 100; // 0.01%
const FEE_TIER_MEDIUM = 500; // 0.05%
const FEE_TIER_STANDARD = 3000; // 0.3%
const FEE_TIER_HIGH = 10000; // 1%
async function swapExactInputSingle() {
const walletClient = createWalletRpcClient(CHAIN_ID, PRIVATE_KEY);
const publicClient = walletClient as any;
const account = walletClient.account?.address;
if (!account) {
throw new Error('No account available');
}
const routerAddress = getUniswapSwapRouter02(CHAIN_ID);
// Token configuration
const tokenIn = getTokenMetadata(CHAIN_ID, 'USDC');
const tokenOut = getTokenMetadata(CHAIN_ID, 'WETH');
// Swap parameters
const amountIn = parseTokenAmount('1000', tokenIn.decimals); // 1000 USDC
const slippageTolerance = 50; // 0.5% in basis points (adjust based on market conditions)
const fee = FEE_TIER_STANDARD; // 0.3% fee tier (most liquid for major pairs)
const deadline = BigInt(Math.floor(Date.now() / 1000) + 600); // 10 minutes
console.log(`Swapping ${amountIn} ${tokenIn.symbol} for ${tokenOut.symbol}`);
console.log(`Router: ${routerAddress}`);
console.log(`Fee tier: ${fee} (0.3%)`);
console.log(`Slippage tolerance: ${slippageTolerance / 100}%`);
// Step 1: Get quote (in production, use QuoterV2 contract)
// For now, we'll set amountOutMinimum to 0 (not recommended in production!)
// In production, always query the pool first to get expected output
const amountOutMinimum = 0n; // TODO: Query QuoterV2 for expected output and apply slippage
// Step 2: Approve token spending
console.log('\n1. Approving token spending...');
const approveTx = await walletClient.writeContract({
address: tokenIn.address,
abi: ERC20_ABI,
functionName: 'approve',
args: [routerAddress, amountIn],
});
await waitForTransaction(publicClient, approveTx);
console.log(`Approved: ${approveTx}`);
// Step 3: Execute swap
console.log('\n2. Executing swap...');
const swapTx = await walletClient.writeContract({
address: routerAddress,
abi: SWAP_ROUTER_ABI,
functionName: 'exactInputSingle',
args: [
{
tokenIn: tokenIn.address,
tokenOut: tokenOut.address,
fee,
recipient: account,
deadline,
amountIn,
amountOutMinimum,
sqrtPriceLimitX96: 0n, // No price limit
},
],
});
await waitForTransaction(publicClient, swapTx);
console.log(`Swap executed: ${swapTx}`);
console.log('\n✅ Swap completed successfully!');
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
swapExactInputSingle().catch(console.error);
}
export { swapExactInputSingle };