Initial commit: add .gitignore and README
This commit is contained in:
225
examples/subgraphs/aave-positions.graphql
Normal file
225
examples/subgraphs/aave-positions.graphql
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
146
examples/subgraphs/cross-protocol-analytics.graphql
Normal file
146
examples/subgraphs/cross-protocol-analytics.graphql
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
137
examples/subgraphs/uniswap-v3-pools.graphql
Normal file
137
examples/subgraphs/uniswap-v3-pools.graphql
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
116
examples/ts/aave-flashloan-multi.ts
Normal file
116
examples/ts/aave-flashloan-multi.ts
Normal 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 };
|
||||
|
||||
104
examples/ts/aave-flashloan-simple.ts
Normal file
104
examples/ts/aave-flashloan-simple.ts
Normal 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 };
|
||||
|
||||
96
examples/ts/aave-pool-discovery.ts
Normal file
96
examples/ts/aave-pool-discovery.ts
Normal 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 };
|
||||
|
||||
161
examples/ts/aave-supply-borrow.ts
Normal file
161
examples/ts/aave-supply-borrow.ts
Normal 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 };
|
||||
|
||||
176
examples/ts/compound3-supply-borrow.ts
Normal file
176
examples/ts/compound3-supply-borrow.ts
Normal 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 };
|
||||
|
||||
82
examples/ts/flashloan-arbitrage.ts
Normal file
82
examples/ts/flashloan-arbitrage.ts
Normal 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 };
|
||||
|
||||
135
examples/ts/protocolink-batch.ts
Normal file
135
examples/ts/protocolink-batch.ts
Normal 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 };
|
||||
|
||||
114
examples/ts/protocolink-compose.ts
Normal file
114
examples/ts/protocolink-compose.ts
Normal 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 };
|
||||
|
||||
116
examples/ts/protocolink-with-permit2.ts
Normal file
116
examples/ts/protocolink-with-permit2.ts
Normal 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 };
|
||||
|
||||
132
examples/ts/supply-borrow-swap.ts
Normal file
132
examples/ts/supply-borrow-swap.ts
Normal 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 };
|
||||
|
||||
131
examples/ts/uniswap-permit2.ts
Normal file
131
examples/ts/uniswap-permit2.ts
Normal 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 };
|
||||
|
||||
136
examples/ts/uniswap-universal-router.ts
Normal file
136
examples/ts/uniswap-universal-router.ts
Normal 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 };
|
||||
|
||||
186
examples/ts/uniswap-v3-oracle.ts
Normal file
186
examples/ts/uniswap-v3-oracle.ts
Normal 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 };
|
||||
|
||||
162
examples/ts/uniswap-v3-swap.ts
Normal file
162
examples/ts/uniswap-v3-swap.ts
Normal 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 };
|
||||
|
||||
Reference in New Issue
Block a user