Nexera-Fi SDKAdvanced Examples

Stream Transactions

In this example, we'll showcase the execution of multiple actions targeting various DeFi protocols within a single
on-chain transaction
, assuming that the strategy Orchestrator instance is already deployed and configured.
(see Deployment & Configuration Guide, Initializing Adapters)

Specifically, the set of actions in this example is the following:

  • Swap WETH for USDT in Uniswap V3 (1st action)
  • Depositing USDT in Nxra DEX (2nd action)
  • Exchange USDT for NXRA in Nxra DEX (3rd action)

Typescript Example

//--------------- Configurations - Set up
const executorPK = process.env.PRIVATE_KEY_EXECUTOR as ethers.BytesLike; // Private key of the designated executor account
 
const chainId = 42161; // Arbitrum One
const rpcServerUrl = "https://..."; // Replace with the RPC server URL
 
const provider = new ethers.providers.JsonRpcProvider(rpcServerUrl); // Create a `Provider` instance
const wallet = new ethers.Wallet(executorPK, provider); // Create a `Signer` instance
 
// ---------------- Target Assets
// Note: Assets at play should be already whitelisted in the Orchestrator
const WETH = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"; 
const USDT = "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9";  
const NXRA = "0x644192291cc835A93d6330b24EA5f5FEdD0eEF9e"; 
 
// Target Adapters (i.e. UniswapAdapter & NxraDEXAdapter)
const UniswapAdapter = nexeraFiSdk.getAdapters(chainId).UniswapAdapter;
const NxraDexAdapter = nexeraFiSdk.getAdapters(chainId).NxraDEXAdapter;
 
// Connect to your Orchestrator instance
const orchestratorAddress = "0x..."; // Replace with the addrress of the target Orchestrator instance you own 
const Orchestrator = nexeraFiSdk.getOrchestrator(orchestratorAddress, wallet);
 
/*
    At this point, it is assumed that:
     - Orchestrator instance holds some WETH balance (i.e. 0.021 WETH)
     - Orchestrator instance has whitelisted the assets at play (USDT, WETH, NXRA)
     - Orchestrator instance has whitelisted and initialized target Adapters (UniswapAdapter & NxraDEXAdapter)
*/
 
// --------------------     1st Action : SWAP WETH for USDT (Uniswap Adapter)     -----------------------------
 
// ------------------- Construct `out` & `expectedIn` token compositions, and `extraData` 
// Tokens to send (i.e. 0.021 WETH)
const out_1 = [
    {
        'token': WETH,
        'amount': ethers.utils.parseEther("0.021")  // WETH has 18 decimals, thus we parse 18 decimals
    },
]
 
// Tokens to receive (i.e. USDT)
// `amount` field is for the Minimum amount of tokens expected to be received
const expectedIn_1 = [
    // USDT has 6 decimals, thus we parse with  -> 6 decimals
    {'token': USDT, 'amount': ethers.utils.parseUnits("49", 6)} 
]
 
const extraData_1 = ethers.utils.defaultAbiCoder.encode(
    ["uint24", "uint160"],
    // fee = 0.5%, sqrtPriceLimitX96 = 0
    [500, 0]
);
 
// --------------------     2nd Action : DEPOSIT USDT IN Nxra DEX (NxraDEX Adapter)     ------------------------
 
// ------------------- Construct `out` & `expectedIn` token compositions, and `extraData` 
// Tokens to send --> deposit (i.e. 49 USDT)
const out_2 = [
    {
        'token': USDT,
        'amount': ethers.utils.parseUnits("49", 6)  // USDT has 6 decimals, thus we parse with 6 decimals
    },
]
 
// Not Used In DEPOSIT Operation for NxraDEX
const expectedIn_2 = []
 
// Not Used In DEPOSIT Operation for NxraDEX
const extraData_2 = "0x";
 
// --------------------    3rd Action : TAKE EXACT TAKE In Nxra DEX (NxraDEX Adapter)     -----------------------------
 
// Not Used In OPERATE.TAKE_EXACT_TAKE Operation for NxraDEX
const out_3 = []
 
// Not Used In OPERATE.TAKE_EXACT_TAKE Operation for NxraDEX
const expectedIn_3 = []
 
const takeExactTakeData = ethers.utils.defaultAbiCoder.encode(
    ["tuple(address,address,uint256,uint256,uint256,uint256,uint256,address,bool)"],
    // makeToken, takeToken, maxTakeAmount, minTakeAmount, maxMakePrice, maxTakePrice, maxIterations, receiver, toBalance
    [[NXRA, USDT, ethers.utils.parseUnits("49", 6), ethers.utils.parseUnits("49", 6), ethers.utils.parseEther("1"), ethers.utils.parseEther("1"), 1000, orchestratorAddress, false]]
)
 
const extraData_3 = ethers.utils.defaultAbiCoder.encode(
    ["tuple(uint256,bytes,bool)"],
    // operation, operationData, fromBalance
    [[NxraDexAdapter.operations.OPERATE.TAKE_EXACT_TAKE, takeExactTakeData, false]]
);
 
// Use `callStatic` method to simulate the Tx (3 actions) and acquire what is received (asset-wise) by the Orchestrator
const totalReceived = await Orchestrator.callStatic.execute(
    [   
        // 1st Action (SWAP UniswapAdapter)
        {
            'adapter': UniswapAdapter.address,
            'op': OPERATION.SWAP,
            'send': out_1,
            'minReceive': expectedIn_1,
            'extraData': extraData_1
        },
        
        // 2nd Action (DEPOSIT NxraDEX)
        {
            'adapter': NxraDexAdapter.address,
            'op': OPERATION.DEPOSIT,
            'send': out_2,
            'minReceive': expectedIn_2,
            'extraData': extraData_2
        },
        
        // 3rd Action (TAKE EXACT TAKE NxraDEX)
        {
            'adapter': NxraDexAdapter.address,
            'op': OPERATION.OPERATE,
            'send': out_3,
            'minReceive': expectedIn_3,
            'extraData': extraData_3
        }
    ]
);
 
// Execute the Tx  ------------------------------------------------------------
let nonce_ = await wallet.getTransactionCount(); // Get nonce for Tx
 
const txRes = await Orchestrator.execute(
    [   
        // 1st Action (SWAP UniswapAdapter)
        {
            'adapter': UniswapAdapter.address,
            'op': OPERATION.SWAP,
            'send': out_1,
            'minReceive': expectedIn_1,
            'extraData': extraData_1
        },
        
        // 2nd Action (DEPOSIT NxraDEX)
        {
            'adapter': NxraDexAdapter.address,
            'op': OPERATION.DEPOSIT,
            'send': out_2,
            'minReceive': expectedIn_2,
            'extraData': extraData_2
        },
        
        // 3rd Action (TAKE EXACT TAKE NxraDEX)
        {
            'adapter': NxraDexAdapter.address,
            'op': OPERATION.OPERATE,
            'send': out_3,
            'minReceive': expectedIn_3,
            'extraData': extraData_3
        }
    ],
    {nonce: nonce_}
);
 
// Wait for block confirmations to get the Tx receipt object
const txRec = await txRes.wait();
 
console.log(`Returned from 1st Action: ${totalReceived[0]}`);
console.log(`Returned from 2nd Action: ${totalReceived[1]}`);  
console.log(`Returned from 3rd Action: ${totalReceived[2]}`);

Python Example

load_dotenv(override=True)
config = dotenv_values(".env")
 
# --------------- Configurations - Set up
executor_account_address = "0x...."  # Replace with the address of the designated executor account
executor_pk = config.get("PRIVATE_KEY_EXECUTOR") # Private key of designated executor account
 
chain_id = 42161   # Arbitrum One
 
rpc_server_url = "https://...." # Replace with the rpc server url
 
w3 = Web3(Web3.HTTPProvider(rpc_server_url)) # Instantiate a Web3 HTTP Provider to connect to a blockchain node
 
# ---------------- Target Assets
# Note: Assets at play should be already whitelisted in the Orchestrator
WETH = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1" 
USDT = "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"  
NXRA = "0x644192291cc835A93d6330b24EA5f5FEdD0eEF9e" 
 
# Target Adapters (i.e. UniswapAdapter & NxraDEXAdapter)
Uniswap_Adapter = get_adapter_addresses(chain_id).UniswapAdapter
NxraDEX_Adapter = get_adapter_addresses(chain_id).NxraDEXAdapter
 
OPERATION = get_operations() # Get Operations enum for Orchestrator
ADAPTER_OP = get_base_operations_for_adapters().NxraDexAdapter # Get Operations enum for NxraDEX 
 
# Get Orchestrator instance
orchestrator_address = "0x..." # Replace with the addrress of the target Orchestrator instance
Orchestrator = get_orchestrator(orchestrator_address, w3)
 
"""
    At this point it is assumed that:
     - Orchestrator instance holds some WETH balance (i.e. 0.021 WETH)
     - Orchestrator instance has whitelisted the assets at play (USDT, WETH, NXRA)
     - Orchestrator instance has whitelisted and initialized target Adapters (Uniswap Adapter & NxraDEXAdapter)
"""
 
# --------------------       1st Action : SWAP WETH for USDT (Uniswap Adapter)     ------------------------------
 
# ------------------- Construct `out` & `expectedIn` token compositions, and `extraData` 
# Tokens to send (i.e. 0.021 WETH)
out_1 = [
    {
        'token': WETH,
        'amount': w3.to_wei(0.021, 'ether')  # WETH has 18 decimals, thus we parse with 'currency' == 'ether' -> 18 decimals
    },
]
 
# Tokens to receive (i.e. USDT)
# `amount` field is for the Minimum amount of tokens expected to be received
expectedIn_1 = [
    {'token': USDT, 'amount': w3.to_wei(49, "mwei")}  # USDT has 6 decimals, thus we parse with 'currency' == 'mwei' -> 6 decimals
]
 
# Extra data contains extra arguments used by the function (i.e. expected `fee` and `sqrtPriceLimitX96`)
extraData_1 = "0x" + (eth_abi.encode(
    ["uint24", "uint160"],
    # fee = 0.05%, sqrtPriceLimitX96 = 0
     [500, 0]
    )).hex()
 
# --------------------       2nd Action : DEPOSIT USDT IN Nxra DEX (NxraDEX Adapter)     ------------------------------
 
# ------------------- Construct `out` & `expectedIn` token compositions, and `extraData` 
# Tokens to send --> deposit (i.e. 49 USDT)
out_2 = [
    {
        'token': USDT,
        'amount': w3.to_wei(49, 'mwei')  # USDT has 6 decimals, thus we parse with 'currency' == 'mwei' -> 6 decimals
    },
]
 
# Not Used In DEPOSIT Operation for NxraDEX
expectedIn_2 = []
 
# Not Used In DEPOSIT Operation for NxraDEX
extraData_2 = bytes()
 
 
# --------------------       3rd Action : TAKE EXACT TAKE In Nxra DEX (NxraDEX Adapter)     ------------------------------
 
# Not Used In OPERATE.TAKE_EXACT_TAKE Operation for NxraDEX
out_3 = []
 
# Not Used In OPERATE.TAKE_EXACT_TAKE Operation for NxraDEX
expectedIn_3 = []
 
# We don't convert to hex string with 0x prefix because we want to further encode this value as bytes object type & not as str type
take_exact_take_data = eth_abi.encode(
    ["(address,address,uint256,uint256,uint256,uint256,uint256,address,bool)"],
    # makeToken, takeToken, maxTakeAmount, minTakeAmount, maxMakePrice, maxTakePrice, maxIterations, receiver, toBalance
    [[NXRA, USDT, w3.to_wei(49, "mwei"), w3.to_wei(49, "mwei"), w3.to_wei(1, "ether"), w3.to_wei(1, "ether"), 1000, orchestrator_address, False]]
)
 
# Here we convert to hex string with 0x prefix because we will pass this as input arg to the Action struct
extraData_3 = "0x" + eth_abi.encode(
    ["(uint256,bytes,bool)"],
    # operation, operationData, fromBalance
    [[ADAPTER_OP.OPERATE.TAKE_EXACT_TAKE.value, take_exact_take_data, False]]
).hex()
 
#  Use `call()` method to simulate the Tx (3 actions) and acquire what is received (asset-wise) by the Orchestrator
total_received = Orchestrator.functions.execute(
    [   # 1st Action (SWAP UniswapAdapter)
        {
            'adapter': Uniswap_Adapter.address,
            'op': OPERATION.SWAP.value,
            'send': out_1,
            'minReceive': expectedIn_1,
            'extraData': extraData_1
        },
        
        # 2nd Action (DEPOSIT NxraDEX)
        {
            'adapter': NxraDEX_Adapter.address,
            'op': OPERATION.DEPOSIT.value,
            'send': out_2,
            'minReceive': expectedIn_2,
            'extraData': extraData_2
        },
        
        #3rd Action (TAKE EXACT TAKE NxraDEX)
        {
            'adapter': NxraDEX_Adapter.address,
            'op': OPERATION.OPERATE.value,
            'send': out_3,
            'minReceive': expectedIn_3,
            'extraData': extraData_3
        }
    ]
).call({"from": executor_account_address})
 
print(f"Returned from 1st Action: {total_received[0]}")
print(f"Returned from 2nd Action: {total_received[1]}")   
print(f"Returned from 3rd Action: {total_received[2]}")   
 
# Execute the Tx  ------------------------------------------------------------
 
# Get nonce for the Tx
nonce_ = w3.eth.get_transaction_count(executor_account_address)
 
# Build Tx request object
transaction = Orchestrator.functions.execute(
    [   # 1st Action (SWAP UniswapAdapter)
        {
            'adapter': Uniswap_Adapter.address,
            'op': OPERATION.SWAP.value,
            'send': out_1,
            'minReceive': expectedIn_1,
            'extraData': extraData_1
        },
        
        # 2nd Action (DEPOSIT NxraDEX)
        {
            'adapter': NxraDEX_Adapter.address,
            'op': OPERATION.DEPOSIT.value,
            'send': out_2,
            'minReceive': expectedIn_2,
            'extraData': extraData_2
        },
        
        #3rd Action (TAKE EXACT TAKE NxraDEX)
        {
            'adapter': NxraDEX_Adapter.address,
            'op': OPERATION.OPERATE.value,
            'send': out_3,
            'minReceive': expectedIn_3,
            'extraData': extraData_3
        }
    ]
).build_transaction(
    {"chainId": chain_id, "from": executor_account_address, "nonce": nonce_}
)
 
# Sign Tx request object
signed_tx = w3.eth.account.sign_transaction(transaction, private_key=executor_pk)
 
# Send the signed Tx request object to the blockchain for resolution
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
 
# Wait for block confirmations to get the Tx receipt object
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
 
print(f"\nTx Receipt:\n\n{tx_receipt}")

On this page