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}")