From 2b3c5021b2d28639b7a12ffdf16ef65aa31303a4 Mon Sep 17 00:00:00 2001 From: none00y Date: Wed, 26 Jun 2024 16:34:42 +0200 Subject: [PATCH 1/2] add vara sdk docs --- docs/docs/vara/installation.md | 28 +- docs/docs/vara/sdk.md | 777 +++++++++++++++++++++++++++++++++ 2 files changed, 801 insertions(+), 4 deletions(-) create mode 100644 docs/docs/vara/sdk.md diff --git a/docs/docs/vara/installation.md b/docs/docs/vara/installation.md index a9965c68..8718f3fd 100644 --- a/docs/docs/vara/installation.md +++ b/docs/docs/vara/installation.md @@ -15,9 +15,14 @@ This section provides detailed instructions on how to install the Invariant prot ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` -#### Add wasm32 target +#### Install rust and add wasm32 target ```bash -rustup target add wasm32-unknown-unknown +rustup install 1.78 +rustup target add wasm32-unknown-unknown --toolchain 1.78 +``` +#### Install wasm-opt +```bash +cargo install wasm-opt --locked ``` ## Build Protocol @@ -31,18 +36,33 @@ git clone git@github.com:invariant-labs/protocol-vara.git #### Build contract ```bash -cargo build +chmod +x ./build.sh +./build.sh dev ``` #### Build in release mode ```bash -cargo contract build --release +chmod +x ./build.sh +./build.sh ``` #### Run tests ```bash +chmod +x ./tests.sh ./tests.sh ``` +## SDK +To build SDK go to the dedicated folder [SDK](https://github.com/invariant-labs/protocol-vara/tree/master/sdk) +#### Build sdk +```bash +chmod +x ./build.sh +./build.sh +``` +#### Run sdk tests +```bash +chmod +x ./tests.sh +./tests.sh +``` \ No newline at end of file diff --git a/docs/docs/vara/sdk.md b/docs/docs/vara/sdk.md new file mode 100644 index 00000000..fbc1e005 --- /dev/null +++ b/docs/docs/vara/sdk.md @@ -0,0 +1,777 @@ +--- +title: SDK + +slug: /vara/sdk +--- + +SDK can be used to interact with our DEX programmatically. It provides an easy way to integrate your app with Invariant and GRC-20 tokens. + +## Installation + +### Download + +Published package can be found [here](https://www.npmjs.com/package/@invariant-labs/vara-sdk). + +### Build + +You can find build steps [here](installation.md). + +## Overview + +The Invariant SDK comprises two distinct contracts: + +1. DEX Contract (Invariant): This is the contract handling DEX functionality within the Invariant ecosystem. + +2. Token Contract (GRC-20): This contract is responsible for managing and implementing the GRC-20 token standard within the Invariant protocol. Allows to deploy or load existing contracts. + +### Transactions and Queries + +When working with contracts, developers can initiate interactions by calling methods from the corresponding contract class. The first parameter designates the account, and subsequent parameters act as entrypoint parameters. + +1. **Transactions**: These involve invoking methods that result in changes to the blockchain state. Transactions typically alter the data stored on the blockchain and may include operations like transferring assets, updating records, or executing specific actions. Once the transaction will be confirmed it returns a message. If the entrypoint has emitted any events, they can be caught with appropriate event listener using `Invariant.on` function. Additionally some entrypoints return results of their computations. + +2. **Queries**: Queries are read-only interactions with the contract. They allow developers to retrieve information from the blockchain without modifying its state. Queries are useful for obtaining current contract state, or verifying certain conditions. They provide results in the form of structs from [storage](storage.md) or estimated results of transaction. + +### Listening to Events + +The Invariant class offers additional methods empowering developers to attach event listeners. This feature enables the execution of custom code based on contract activity, enhancing flexibility and customization in response to specific events. + +### Values and Helper functions + +The SDK includes fundamental values and utility functions for application development. These encompass parameters such as maximum tick, maximum price, and calculations for price impact. + +### Contracts Metadata + +Within the Contracts folder, developers can find deploy-ready wasm files and idl files essential for sails-js code generation. + +### Wasm + +The Wasm functionality is encapsulated within our custom npm package that is a part of the workspace, which includes Rust structs and data exported to WebAssembly using wasm_bindgen. This integration facilitates the utilization of identical functions and data within the main Invariant contract. + +### Source + +The Source directory consolidates all pieces into an easy-to-use interface. This organized structure simplifies the development process and provides developers with a centralized location for accessing essential resources. + +### Tests + +End-to-end (E2E) tests are an essential component of our testing strategy. We have adopted the ts-mocha framework for our end-to-end testing needs. For assertion and expectation handling, we rely on the Chai assertion library. Our end-to-end tests encompass a comprehensive examination of the entire protocol. This includes testing all entrypoints of every contract within our protocol, ensuring that each contract behaves as expected under various conditions. Additionally, we thoroughly test our SDK math utilities to guarantee their accuracy and reliability. + +### Project Structure + +``` +📦sdk + ┣ 📂contracts + ┃ ┣ 📂invariant + ┃ ┣ 📂gear_erc20 + ┣ 📂src + ┗ 📂tests +``` + +## Types + +### Decimal + +These types are utilized to represent decimal values, and it is worth noting that these decimal types are already exported from Rust through WebAssembly (Wasm) using wasm-bindgen. Each type in this collection comes with associated decimal scales and functionalities, allowing for precise and reliable handling of decimal calculations. Read more about [decimals](types.md). + +```typescript +export type TokenAmount = bigint + +export type Liquidity = bigint + +export type FeeGrowth = bigint + +export type SqrtPrice = bigint + +export type Price = bigint + +export type FixedPoint = bigint + +export type Percentage = bigint +``` + +### Network + +Network serves as a convenient way to select the network on which various actions are performed. The enumeration includes options for 'local', 'mainnet', and 'testnet'. Users can switch between networks without the need of any code adjustments. + +```typescript +enum Network { + Local, + Testnet, + Mainnet +} +``` + +### Events + +Code snippet introduces several event interfaces related to the Invariant, providing structured information about various actions. These events can be subscribed to using the Invariant, enabling users to receive updates when specific actions occur within the contract. + +```typescript +interface PositionCreatedEvent { + timestamp: bigint + address: string + poolKey: PoolKey + liquidityDelta: Liquidity + lowerTick: bigint + upperTick: bigint + sqrtPrice: SqrtPrice +} + +interface CrossTickEvent { + timestamp: bigint + address: string + poolKey: PoolKey + indexes: bigint[] +} + +interface PositionRemovedEvent { + timestamp: bigint + address: string + poolKey: PoolKey + liquidityDelta: Liquidity + lowerTick: bigint + upperTick: bigint + sqrtPrice: SqrtPrice +} + +interface SwapEvent { + timestamp: bigint + address: string + poolKey: PoolKey + amountIn: TokenAmount + amountOut: TokenAmount + fee: TokenAmount + startSqrtPrice: SqrtPrice + targetSqrtPrice: SqrtPrice + xToY: boolean +} +``` + +### Storage + +These interfaces are essential for managing various aspects of the Invariant's storage. It is important to note that these interfaces are exported from Rust to TypeScript using wasm-bindgen, providing integration between the two languages. Read more about storage [here](storage.md). + +```typescript +interface InvariantConfig { + admin: string + protocolFee: Percentage +} + +interface FeeTier { + fee: Percentage + tickSpacing: bigint +} + +interface PoolKey { + tokenX: `0x${string}` + tokenY: `0x${string}` + feeTier: FeeTier +} + +interface Pool { + liquidity: Liquidity + sqrtPrice: SqrtPrice + currentTickIndex: bigint + feeGrowthGlobalX: FeeGrowth + feeGrowthGlobalY: FeeGrowth + feeProtocolTokenX: TokenAmount + feeProtocolTokenY: TokenAmount + startTimestamp: bigint + lastTimestamp: bigint + feeReceiver: string +} + +interface Position { + poolKey: PoolKey + liquidity: Liquidity + lowerTickIndex: bigint + upperTickIndex: bigint + feeGrowthInsideX: FeeGrowth + feeGrowthInsideY: FeeGrowth + lastBlockNumber: bigint + tokensOwedX: TokenAmount + tokensOwedY: TokenAmount +} + +interface Tick { + index: bigint + sign: boolean + liquidityChange: Liquidity + liquidityGross: Liquidity + sqrtPrice: SqrtPrice + feeGrowthOutsideX: FeeGrowth + feeGrowthOutsideY: FeeGrowth + secondsOutside: bigint +} +``` + +## Usage Guide + +Follow a step-by-step example demonstrating how to use the Invariant SDK, with each step accompanied by code snippets. The complete code for these examples is available [here](https://github.com/invariant-labs/protocol-vara/blob/master/sdk/tests/example.test.ts), ensuring a hands-on and comprehensive understanding of the SDK's functionality. + +### Select Network + +Begin by specifying the network you intend to connect to using the Network enum. Identify your target network, whether it's the local development environment, the mainnet, or a testnet. The code is designed to work uniformly across all networks. Changing the network designation does not require any modifications to the code. + +```typescript +enum Network { + Local, + Testnet, + Mainnet +} +``` + +### Initlialize API + +:::note Running Web Assembly +SDK utilizes Web Assembly. In order to run WASM files in Node.js you have to add `--experimental-wasm-modules` flag. +::: + +Initiate the Polkadot API effortlessly with the provided `initGearApi` function. Use the `Network` enum to specify your desired network. + +```typescript +// initialize api, use enum to specify the network +const api = await initGearApi({ providerAddress: Network.Local }) +``` + +### Transaction Signer + +Utilize the versatile `GearKeyring` class to easily create and manage accounts. Initialize an account using your preferred method, whether it's using a provided mnemonic phrase or integrating your existing wallet. + +```typescript +// initialize account, you can use your own wallet by pasting mnemonic phase +const admin = await GearKeyring.fromSuri('//Bob') +``` + +### GRC-20 token + +In the following TypeScript code, we demonstrate approach deploying and initializing a GRC-20 token contracts using the `FungibleToken.deploy` method. Apart from the deployment and initialization, the code also demonstrates how to fetch token metadata. This can include details such as the token name, symbol, token decimal. + +```typescript +// deploy token, it will return token object +const token0 = await FungibleToken.deploy(api, admin, 'CoinA', 'ACOIN', 12n) +// token address can be accessed by calling programId method +const token1Address = ( + await FungibleToken.deploy(api, admin, 'CoinB', 'BCOIN', 12n) +).programId() + +// load token by passing its address (you can use existing one), it allows you to interact with it +// eslint disable-next-line +const token1 = await FungibleToken.load(api, token1Address) + +// interact with token 0 +const admin0Balance = await token0.balanceOf(admin.addressRaw) +console.log(admin0Balance) + +// if you want to interact with different token, +// simply pass different contract address as an argument +const admin1Balance = await token1.balanceOf(admin.addressRaw) +console.log(admin1Balance) + +// fetch token metadata for previously deployed token0 +const token0Name = await token0.name() +const token0Symbol = await token0.symbol() +const token0Decimals = await token0.decimals() +console.log(token0Name, token0Symbol, token0Decimals) + +// load diffrent token and load its metadata +const token1Name = await token1.name() +const token1Symbol = await token1.symbol() +const token1Decimals = await token1.decimals() +console.log(token1Name, token1Symbol, token1Decimals) +``` + +:::tip Output +0n
+0n
+CoinA ACOIN 12n
+CoinB BCOIN 12n
+::: + +### Load DEX and tokens + +:::note Deploying and loading GRC-20 contracts +The deploy function serves as a one-stop solution for deploying GRC-20 contracts. When invoked, returns a unique contract address. This address serves as a reference for the deployed contract. +By providing the contract address returned during deployment, the load function dynamically adds all necessary methods to the specified contract. This dynamic loading capability ensures that the contract is equipped with the essential functionalities defined by the GRC-20 standard. +::: + +Load the Invariant contract by providing the Polkadot API (`api`), and indicating the Invariant contract address (`INVARIANT_ADDRESS`). Similarly, initialize the GRC-20 token contract using the same approach. + +```typescript +// load invariant contract +const invariant = await Invariant.load(api, INVARIANT_ADDRESS) + +// load token contract +const grc20 = await FungibleToken.load(api, TOKEN0_ADDRESS) +``` + +### Create pool + +:::info Big numbers +You can create custom decimals using the `toDecimal` syntax, where the first argument represents the numerical value (A), and the second argument indicates the power of 10 (B) in the formula `A * 10^(-B)`. For example, `toDecimal(3n, 2n)` will result in a decimal equal to 0.03. For further details on supported types, please check the documentation [here](types.md). +::: + +:::note Why "n" is at the end of every number +Notice how we use "n" at the end of every number. "n" indicates that specified value is a BigInt, number with higher precision. +::: + +:::warning Token sorting +Tokens are sorted alphabetically when pool key is created, so make sure that you swap tokens in correct direction. Read more about pool keys [here](storage#poolkey). +::: + +To create a new pool, a fee tier and pool key need to be prepared. The fee tier represents the desired fee for the pool, and the price needs to be converted to sqrt price because the entry points of the protocol accept it in this format. The pool key is constructed using the addresses of two tokens and the specified fee tier. Finally, the `createPool` function is called with the user's account, the pool key, and the initial square root price, resulting in the creation of a new pool + +```typescript +// set fee tier, make sure that fee tier with specified parameters exists +const feeTier = newFeeTier(toPercentage(1n, 2n), 1n) // fee: 0.01 = 1%, tick spacing: 1 + +// set initial price of the pool, we set it to 1.00 +// all endpoints only accept sqrt price so we need to convert it before passing it +const price = toPrice(1n, 0n) +const initSqrtPrice = priceToSqrtPrice(price) + +// set pool key, make sure that pool with specified parameters does not exists +const poolKey = newPoolKey(tokenX.programId(), TOKEN1_ADDRESS, feeTier) + +await invariant.createPool(account, poolKey, initSqrtPrice) +``` + +### Deposit + +These entrypoints allow for depositing tokens that will be used for the future operations within the DEX, it's necessary for making the contract atomic. After the operation has been performed tokens may be withdrawn or used in future operations. Return value of these entrypoints contains the amount of tokens deposited in the same order that they were provided. +```typescript +// deposit both tokens at once +const depositResult = await invariant.depositTokenPair(admin, [tokenX.programId(), tokenXAmount], [tokenY.programId(), tokenYAmount]) +console.log(depositResult) +// deposit single token +const depositResult = await invariant.depositSingleToken(admin, tokenX.programId(), tokenXAmount) +console.log(depositResult) +``` + +:::tip Output +
+[100n, 100n]
+100n
+::: + + +### Withdraw +These entrypoints allow for funds withdrawal after performing desired operations. `null` may be passed instead of the amount to withdraw the current balance without having to query the state. Return value of these entrypoints contains the amount of tokens withdrawn in the same order that they were provided. +```typescript +// withdraw both tokens at once +const withdrawResult = await invariant.withdrawTokenPair(admin, [tokenX.programId(), tokenXAmount], [tokenY.programId(), tokenYAmount]) +console.log(withdrawResult) +// withdraw single token +const withdrawResult = await invariant.withdrawSingleToken(admin, tokenX.programId(), tokenXAmount) +console.log(withdrawResult) +// withdraw both tokens at once without knowing the amount of the tokens in the contract +const withdrawResult = await invariant.withdrawTokenPair(admin, [tokenX.programId(), null], [tokenY.programId(), null]) +console.log(withdrawResult) +// withdraw single token without knowing the amount of the tokens in the contract +const withdrawResult = await invariant.withdrawSingleToken(admin, tokenX.programId(), null) +console.log(withdrawResult) +``` + +:::tip Output +
+[100n, 100n]
+100n
+[1000n, 0n]
+[10n]
+::: + +### Create position + +:::info How to calculate input amount +In order to calculate input amount, we have to multiply actual token amount you want to swap times 10 to the power of decimal. +Let's say some token has decimal of 12 and we want to swap 6 actual tokens. Here is how we can calculate input amount: +6 \* 10^12 = 6000000000000. +::: + +Creating position involves preparing parameters such as the amount of tokens, tick indexes for the desired price range, liquidity, slippage and approving token transfers. There is need to calculate desired liquidity based on specified token amounts. For this case there are provided functions `getLiquidityByX` or `getLiquidityByY`. The slippage parameter represents the acceptable price difference that can occur on the pool during the execution of the transaction. + +```typescript +// token y has 12 decimals and we want to add 8 integer tokens to our position +const tokenYAmount = 8n * 10n ** 12n + +// set lower and upper tick indexes, we want to create position in range [-10, 10] +const lowerTickIndex = -10n +const upperTickIndex = 10n + +// calculate amount of token x we need to give to create position +const { amount: tokenXAmount, l: positionLiquidity } = getLiquidityByY( + tokenYAmount, + lowerTickIndex, + upperTickIndex, + initSqrtPrice, + true +) + +// print amount of token x and y we need to give to create position based on parameteres we passed +console.log(tokenXAmount, tokenYAmount) + +// approve transfers of both tokens +await token0.approve(account, invariant.addressRaw, tokenXAmount) +await token1.approve(account, invariant.addressRaw, tokenYAmount) + +// deposit both tokens at once +await invariant.depositTokenPair(admin, [tokenX.programId(), tokenXAmount], [tokenY.programId(), tokenYAmount]) + +// create position +const createPositionResult = await invariant.createPosition( + account, + poolKey, + lowerTickIndex, + upperTickIndex, + positionLiquidity, + initSqrtPrice, + 0n +) + +console.log(createPositionResult) + +// withdraw tokens from the contract +// passing null will try to withdraw all tokens in case no tokens are deposited +const withdrawResult = await invariant.withdrawTokenPair( + admin, + [poolKey.tokenX, null], + [poolKey.tokenY, null] +) +console.log(withdrawResult) + +``` + +:::tip Output +7999999999880n 8000000000000n
+{
+  poolKey: {
+    tokenX: '0x3e3d8e1e8508bbac3eb4d9a5f080a0bc05e01ed49851b63fa7e6ee27ae5ea412',
+    tokenY: '0xe5131b8a771fe50e815b758f1a8f436247c4d8affcdcf0934f7701eb4a279a94',
+    feeTier: { fee: 6000000000n, tickSpacing: 10 }
+  },
+  liquidity: 1000000000000n,
+  lowerTickIndex: -20n,
+  upperTickIndex: 10n,
+  feeGrowthInsideX: 0n,
+  feeGrowthInsideY: 0n,
+  lastBlockNumber: 352n,
+  tokensOwedX: 0n,
+  tokensOwedY: 0n
+}
+::: + +### Swap tokens + +Performing a swap requires: specifying the amount of tokens to be swapped or desired amount to receive from the swap (input token amount will be calculated durning the swap), approving the transfer of the token, estimating the result of the swap, direction, determining the allowed slippage, calculating the square root price limit based on slippage, and finally, executing the swap. It's essential to note that the swap tolerance is expressed in square root price (sqrtPrice) after the swap, rather than the amount of tokens. + +:::note Price impact and slippage +Price impact represents the change in price observed after the completion of a swap. It provides insight into how the executed swap influences the token price within the liquidity pool. A higher price impact indicates a more significant alteration in the token price post-swap. + +Slippage refers to the difference between the estimated square root of price after a swap is initiated and the actual square root of price observed after the swap is executed. It quantifies the deviation between the expected and realized square roots of prices. Slippage does not imply an acceptable threshold solely in terms of token amounts. + +While price impact focuses on the post-swap change in token price within the liquidity pool, slippage highlights the variance between the anticipated and actual prices during and after the swap process. +::: + +```typescript +// we want to swap 6 token0 +// token0 has 12 decimals so we need to multiply it by 10^12 +const amount = 6n * 10n ** 12n + +// approve token x transfer +await tokenX.approve(admin, invariant.programId(), amount) +// deposit tokenX +await invariant.depositSingleToken(admin, poolKey.tokenX, amount) + +// get estimated result of swap +const quoteResult = await invariant.quote(poolKey, true, amount, true) + +// slippage is a price change you are willing to accept, +// for examples if current price is 1 and your slippage is 1%, then price limit will be 1.01 +const allowedSlippage = toPercentage(1n, 3n) // 0.001 = 0.1% + +// calculate sqrt price limit based on slippage +const sqrtPriceLimit = calculateSqrtPriceAfterSlippage( + quoteResult.targetSqrtPrice, + allowedSlippage, + false +) + +const swapResult = await invariant.swap(admin, poolKey, true, amount, true, sqrtPriceLimit) +console.log(swapResult) + +await invariant.withdrawSingleToken(admin, poolKey.tokenX, null) +``` +:::tip Output +{
+  amountIn: 6000000000000n,
+  amountOut: 5937796254308n,
+  startSqrtPrice: 1000000000000000000000000n,
+  targetSqrtPrice: 999628999041807638582903n,
+  fee: 60000000000n,
+  pool: {
+    liquidity: 16004800319759905588483n,
+    sqrtPrice: 999628999041807638582903n,
+    currentTickIndex: -8n,
+    feeGrowthGlobalX: 37488752625000000000000n,
+    feeGrowthGlobalY: 0n,
+    feeProtocolTokenX: 0n,
+    feeProtocolTokenY: 0n,
+    startTimestamp: 1719399192000n,
+    lastTimestamp: 1719399204000n,
+    feeReceiver: '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d' +  }
, +  ticks: []
+}
+::: + +### List of Queries and Interfaces + +Here are some possible queries and their corresponding TypeScript interfaces: + +- Get Tick + +```typescript +interface Tick { + index: bigint + sign: boolean + liquidityChange: Liquidity + liquidityGross: Liquidity + sqrtPrice: SqrtPrice + feeGrowthOutsideX: FeeGrowth + feeGrowthOutsideY: FeeGrowth + secondsOutside: bigint +} + +const tickState: Tick = await invariant.getTick(poolKey, tickIndex) +``` + +- Get Pool + +```typescript +interface Pool { + liquidity: Liquidity + sqrtPrice: SqrtPrice + currentTickIndex: bigint + feeGrowthGlobalX: FeeGrowth + feeGrowthGlobalY: FeeGrowth + feeProtocolTokenX: TokenAmount + feeProtocolTokenY: TokenAmount + startTimestamp: bigint + lastTimestamp: bigint + feeReceiver: string +} + +const poolState: Pool = await invariant.getPool(TOKEN0_ADDRESS, TOKEN1_ADDRESS, feeTier) +``` + +- Get All Pools + +```typescript +const pools: PoolKey[] = await invariant.getPools(1n, 0n) +``` + +:::note Position indexing +Remember that positions are indexed from 0. So if you create position, its index will be 0 and your next positions index will be 1. +::: + +- Get Position + +```typescript +interface Position { + poolKey: PoolKey + liquidity: Liquidity + lowerTickIndex: bigint + upperTickIndex: bigint + feeGrowthInsideX: FeeGrowth + feeGrowthInsideY: FeeGrowth + lastBlockNumber: bigint + tokensOwedX: TokenAmount + tokensOwedY: TokenAmount +} + +const positionState: Position = await invariant.getPosition(owner.addressRaw, positionIndex) +``` + +### Query states and Calculate Fee + +To query the state and calculate **unclaimed** fees **belonging to the position**, several functions are utilized. Positions, ticks, and pools are accessed to gather information about the state, and the calculateFee function is used to determine the amount of unclaimed tokens. + +```typescript +// query states +const pool: Pool = await invariant.getPool(TOKEN0_ADDRESS, TOKEN1_ADDRESS, feeTier) +const position: Position = await invariant.getPosition(account.addressRaw, 0n) +const lowerTick: Tick = await invariant.getTick(poolKey, position.lowerTickIndex) +const upperTick: Tick = await invariant.getTick(poolKey, position.upperTickIndex) + +// check amount of tokens is able to claim +const fees = calculateFee(pool, position, lowerTick, upperTick) + +// print amount of unclaimed x and y token +console.log(fees) +``` + +:::tip Output +{x: 59999999999n, y: 0n } +::: + +### Claim fees + +Fees from a specific position are claimed without closing the position. This process involves specifying the position ID (indexed from 0), calling the claimFee function, and then checking the balance of a specific token after claiming the fees. + +```typescript +// get balance of a specific token before claiming position fees and print it +const adminBalanceBeforeClaim = await tokenX.balanceOf(admin.addressRaw) +console.log(adminBalanceBeforeClaim) + +// specify position id +const positionId = 0n +// claim fee +const claimFeeResult = await invariant.claimFee(admin, positionId) +console.log(claimFeeResult) +const withdrawResult = await invariant.withdrawSingleToken(admin, poolKey.tokenX, null) +console.log(withdrawResult) + +// get balance of a specific token after claiming position fees and print it +const adminBalanceAfterClaim = await tokenX.balanceOf(admin.addressRaw) +console.log(adminBalanceAfterClaim) +``` + +:::tip Output +[ 59999999999n, 0n ]
+999999999999999986000000000120n
+[ 59999999999n, 0n ]
+59999999999n
+999999999999999986060000000119n
+::: + +### Transfer position + +The entrypoint facilitates the seamless transfer of positions between users. This functionality streamlines the process of reassigning ownership of a specific position to another account. The entrypoint takes two parameters: index of position to transfer, address of account to receive the position. + +```typescript +const positionToTransfer = await invariant.getPosition(account.addressRaw, 0n) + +// Transfer position from account (signer) to receiver +await invariant.transferPosition(account, 0n, receiver.addressRaw) + +// load received position +const receiverPosition = await invariant.getPosition(receiver.addressRaw, 0n) + +// ensure that the position are equal +assert.deepEqual(positionToTransfer, receiverPosition) +console.log(receiverPosition) +``` + +:::tip Output +{
+  poolKey: {
+    tokenX: '0xb26c4ab30334ccb23a3023465cb615bb5d0fa4635e9cac7c9428a7f1add9844a',
+    tokenY: '0xb519977de99135feacb3b8c813a62faecfcc263a238a16f58041adcd65fa61f9',
+    feeTier: { fee: 10000000000n, tickSpacing: 1 }
+  },
+  liquidity: 16004800319759905588483n,
+  lowerTickIndex: -10n,
+  upperTickIndex: 10n,
+  feeGrowthInsideX: 37488752625000000000000n,
+  feeGrowthInsideY: 0n,
+  lastBlockNumber: 1665n,
+  tokensOwedX: 0n,
+  tokensOwedY: 0n
+}
+::: + +### Remove position + +Position is removed from the protocol, and fees associated with that position are automatically claimed in the background. Here's a detailed description of the process: + +```typescript + // fetch user balances before removal + const adminToken0BalanceBeforeRemove = await tokenX.balanceOf(admin.addressRaw) + const adminToken1BalanceBeforeRemove = await tokenY.balanceOf(admin.addressRaw) + console.log(adminToken0BalanceBeforeRemove, adminToken1BalanceBeforeRemove) + // remove position + const removePositionResult = await invariant.removePosition(admin, positionId) + console.log(removePositionResult) + + await invariant.withdrawTokenPair(admin, [poolKey.tokenX, null], [poolKey.tokenY, null]) + // get balance of a specific token after removing position + const adminToken0BalanceAfterRemove = await tokenX.balanceOf(admin.addressRaw) + const adminToken1BalanceAfterRemove = await tokenY.balanceOf(admin.addressRaw) + + // print balances + console.log(adminToken0BalanceAfterRemove, adminToken1BalanceAfterRemove) +``` +:::tip Output +999999999999999986060000000119n 999999999999999997937796254308n
+[ 13939999999879n, 2062203745691n ]
+999999999999999999999999999998n 999999999999999999999999999999n
+::: + +### Listen to Event + +Invariant provides the capability to set up event listeners, allowing developers to respond to specific events within the contract. The event listener structs are detailed below, accompanied by a code snippet demonstrating how to set up listeners for each specific event type. + +```typescript +interface PositionCreatedEvent { + timestamp: bigint + address: string + poolKey: PoolKey + liquidityDelta: Liquidity + lowerTick: bigint + upperTick: bigint + sqrtPrice: SqrtPrice +} + +interface CrossTickEvent { + timestamp: bigint + address: string + poolKey: PoolKey + indexes: bigint[] +} + +interface PositionRemovedEvent { + timestamp: bigint + address: string + poolKey: PoolKey + liquidityDelta: Liquidity + lowerTick: bigint + upperTick: bigint + sqrtPrice: SqrtPrice +} + +interface SwapEvent { + timestamp: bigint + address: string + poolKey: PoolKey + amountIn: TokenAmount + amountOut: TokenAmount + fee: TokenAmount + startSqrtPrice: SqrtPrice + targetSqrtPrice: SqrtPrice + xToY: boolean +} +``` + +Developers can define a handler function, such as the handler function in the code snippet, that takes an event parameter matching the structure of the respective event type (e.g., `SwapEvent`). This handler function enables developers to perform actions based on the event details. + +```typescript +const handler = (event: SwapEvent) => { + // checking if swap was made on a specific pool + if (event.poolKey === poolKey) { + // do action + } +} +``` + +The `invariant.on` method allows developers to subscribe to specific events, such as `SwapEvent`, by specifying the event type and providing the corresponding handler function. + +```typescript +// attach event listener to invariant contract and listen for swap events +invariant.on({ident: InvariantEvent.SwapEvent, callback: handler}) +``` + +When event subscriptions are no longer needed, developers can use the `invariant.off` method to unsubscribe from specific events. This ensures efficient resource management and stops further notifications for the specified event type. + +```typescript +// remove event listener when it is no longer needed +invariant.off({ident: InvariantEvent.SwapEvent, callback: handler}) +``` From 8ea8c1b02e6092f72c7223c571a7485cf71759d0 Mon Sep 17 00:00:00 2001 From: none00y Date: Thu, 27 Jun 2024 13:38:55 +0200 Subject: [PATCH 2/2] update token interface in vara sdk docs --- docs/docs/vara/sdk.md | 126 +++++++++++++++++++++++------------------- docs/sidebars.js | 1 + 2 files changed, 69 insertions(+), 58 deletions(-) diff --git a/docs/docs/vara/sdk.md b/docs/docs/vara/sdk.md index fbc1e005..099d94d2 100644 --- a/docs/docs/vara/sdk.md +++ b/docs/docs/vara/sdk.md @@ -247,36 +247,35 @@ const admin = await GearKeyring.fromSuri('//Bob') In the following TypeScript code, we demonstrate approach deploying and initializing a GRC-20 token contracts using the `FungibleToken.deploy` method. Apart from the deployment and initialization, the code also demonstrates how to fetch token metadata. This can include details such as the token name, symbol, token decimal. ```typescript -// deploy token, it will return token object -const token0 = await FungibleToken.deploy(api, admin, 'CoinA', 'ACOIN', 12n) -// token address can be accessed by calling programId method -const token1Address = ( - await FungibleToken.deploy(api, admin, 'CoinB', 'BCOIN', 12n) -).programId() +// deploy token, it will return token address +const token0Address = await FungibleToken.deploy(api, admin, 'CoinA', 'ACOIN', 12n) +const token1Address = await FungibleToken.deploy(api, admin, 'CoinB', 'BCOIN', 12n) -// load token by passing its address (you can use existing one), it allows you to interact with it -// eslint disable-next-line -const token1 = await FungibleToken.load(api, token1Address) +// loading token class, allows you to interact with token contracts +const GRC20 = await FungibleToken.load(api) +// set admin account if you want to mint or burn tokens +// by default admin is set to the deployer of the contract +GRC20.setAdmin(admin) // interact with token 0 -const admin0Balance = await token0.balanceOf(admin.addressRaw) +const admin0Balance = await GRC20.balanceOf(admin.addressRaw, token0Address) console.log(admin0Balance) // if you want to interact with different token, // simply pass different contract address as an argument -const admin1Balance = await token1.balanceOf(admin.addressRaw) +const admin1Balance = await GRC20.balanceOf(admin.addressRaw, token1Address) console.log(admin1Balance) // fetch token metadata for previously deployed token0 -const token0Name = await token0.name() -const token0Symbol = await token0.symbol() -const token0Decimals = await token0.decimals() +const token0Name = await GRC20.name(token0Address) +const token0Symbol = await GRC20.symbol(token0Address) +const token0Decimals = await GRC20.decimals(token0Address) console.log(token0Name, token0Symbol, token0Decimals) // load diffrent token and load its metadata -const token1Name = await token1.name() -const token1Symbol = await token1.symbol() -const token1Decimals = await token1.decimals() +const token1Name = await GRC20.name(token1Address) +const token1Symbol = await GRC20.symbol(token1Address) +const token1Decimals = await GRC20.decimals(token1Address) console.log(token1Name, token1Symbol, token1Decimals) ``` @@ -301,7 +300,7 @@ Load the Invariant contract by providing the Polkadot API (`api`), and indicatin const invariant = await Invariant.load(api, INVARIANT_ADDRESS) // load token contract -const grc20 = await FungibleToken.load(api, TOKEN0_ADDRESS) +const GRC20 = await FungibleToken.load(api) ``` ### Create pool @@ -330,7 +329,7 @@ const price = toPrice(1n, 0n) const initSqrtPrice = priceToSqrtPrice(price) // set pool key, make sure that pool with specified parameters does not exists -const poolKey = newPoolKey(tokenX.programId(), TOKEN1_ADDRESS, feeTier) +const poolKey = newPoolKey(token0Address, token1Address, feeTier) await invariant.createPool(account, poolKey, initSqrtPrice) ``` @@ -340,10 +339,10 @@ await invariant.createPool(account, poolKey, initSqrtPrice) These entrypoints allow for depositing tokens that will be used for the future operations within the DEX, it's necessary for making the contract atomic. After the operation has been performed tokens may be withdrawn or used in future operations. Return value of these entrypoints contains the amount of tokens deposited in the same order that they were provided. ```typescript // deposit both tokens at once -const depositResult = await invariant.depositTokenPair(admin, [tokenX.programId(), tokenXAmount], [tokenY.programId(), tokenYAmount]) +const depositResult = await invariant.depositTokenPair(admin, [tokenXAddress, tokenXAmount], [tokenYAddress, tokenYAmount]) console.log(depositResult) // deposit single token -const depositResult = await invariant.depositSingleToken(admin, tokenX.programId(), tokenXAmount) +const depositResult = await invariant.depositSingleToken(admin, tokenXAddress, tokenXAmount) console.log(depositResult) ``` @@ -358,16 +357,16 @@ console.log(depositResult) These entrypoints allow for funds withdrawal after performing desired operations. `null` may be passed instead of the amount to withdraw the current balance without having to query the state. Return value of these entrypoints contains the amount of tokens withdrawn in the same order that they were provided. ```typescript // withdraw both tokens at once -const withdrawResult = await invariant.withdrawTokenPair(admin, [tokenX.programId(), tokenXAmount], [tokenY.programId(), tokenYAmount]) +const withdrawResult = await invariant.withdrawTokenPair(admin, [tokenXAddress, tokenXAmount], [tokenYAddress, tokenYAmount]) console.log(withdrawResult) // withdraw single token -const withdrawResult = await invariant.withdrawSingleToken(admin, tokenX.programId(), tokenXAmount) +const withdrawResult = await invariant.withdrawSingleToken(admin, tokenXAddress, tokenXAmount) console.log(withdrawResult) // withdraw both tokens at once without knowing the amount of the tokens in the contract -const withdrawResult = await invariant.withdrawTokenPair(admin, [tokenX.programId(), null], [tokenY.programId(), null]) +const withdrawResult = await invariant.withdrawTokenPair(admin, [tokenXAddress, null], [tokenYAddress, null]) console.log(withdrawResult) // withdraw single token without knowing the amount of the tokens in the contract -const withdrawResult = await invariant.withdrawSingleToken(admin, tokenX.programId(), null) +const withdrawResult = await invariant.withdrawSingleToken(admin, tokenXAddress, null) console.log(withdrawResult) ``` @@ -408,17 +407,24 @@ const { amount: tokenXAmount, l: positionLiquidity } = getLiquidityByY( // print amount of token x and y we need to give to create position based on parameteres we passed console.log(tokenXAmount, tokenYAmount) - // approve transfers of both tokens -await token0.approve(account, invariant.addressRaw, tokenXAmount) -await token1.approve(account, invariant.addressRaw, tokenYAmount) +await GRC20.approve(admin, invariant.programId(), tokenXAmount, poolKey.tokenX) +await GRC20.approve(admin, invariant.programId(), tokenYAmount, poolKey.tokenY) -// deposit both tokens at once -await invariant.depositTokenPair(admin, [tokenX.programId(), tokenXAmount], [tokenY.programId(), tokenYAmount]) +// deposit tokens in the contract +await invariant.depositTokenPair( + admin, + [poolKey.tokenX, tokenXAmount], + [poolKey.tokenY, tokenYAmount] +) + +// check user balances before creating position +const userBalances = await invariant.getUserBalances(admin.addressRaw) +console.log(userBalances) // create position const createPositionResult = await invariant.createPosition( - account, + admin, poolKey, lowerTickIndex, upperTickIndex, @@ -427,7 +433,7 @@ const createPositionResult = await invariant.createPosition( 0n ) -console.log(createPositionResult) +console.log(createPositionResult) // print transaction result // withdraw tokens from the contract // passing null will try to withdraw all tokens in case no tokens are deposited @@ -442,10 +448,14 @@ console.log(withdrawResult) :::tip Output 7999999999880n 8000000000000n
+Map(2) {
+  '0x476f15fb07f2c1fa3d2a8212496db9535e9911929760651840c335a48791af5b' => 8000000000000n,
+  '0x439d17e3bad34ca76cca21ec23c1e746673187a9d7da9d65988c55350c5292d1' => 7999999999880n
+}
{
  poolKey: {
-    tokenX: '0x3e3d8e1e8508bbac3eb4d9a5f080a0bc05e01ed49851b63fa7e6ee27ae5ea412',
-    tokenY: '0xe5131b8a771fe50e815b758f1a8f436247c4d8affcdcf0934f7701eb4a279a94',
+    tokenX: '0x439d17e3bad34ca76cca21ec23c1e746673187a9d7da9d65988c55350c5292d1',
+    tokenY: '0x476f15fb07f2c1fa3d2a8212496db9535e9911929760651840c335a48791af5b',
    feeTier: { fee: 6000000000n, tickSpacing: 10 }
  },
  liquidity: 1000000000000n,
@@ -477,7 +487,7 @@ While price impact focuses on the post-swap change in token price within the liq const amount = 6n * 10n ** 12n // approve token x transfer -await tokenX.approve(admin, invariant.programId(), amount) +await GRC20.approve(admin, invariant.programId(), amount, poolKey.tokenX) // deposit tokenX await invariant.depositSingleToken(admin, poolKey.tokenX, amount) @@ -498,7 +508,7 @@ const sqrtPriceLimit = calculateSqrtPriceAfterSlippage( const swapResult = await invariant.swap(admin, poolKey, true, amount, true, sqrtPriceLimit) console.log(swapResult) -await invariant.withdrawSingleToken(admin, poolKey.tokenX, null) +await invariant.withdrawSingleToken(admin, poolKey.tokenY, null) ``` :::tip Output {
@@ -619,7 +629,7 @@ Fees from a specific position are claimed without closing the position. This pro ```typescript // get balance of a specific token before claiming position fees and print it -const adminBalanceBeforeClaim = await tokenX.balanceOf(admin.addressRaw) +const adminBalanceBeforeClaim = await GRC20.balanceOf(admin.addressRaw, token0Address) console.log(adminBalanceBeforeClaim) // specify position id @@ -631,7 +641,7 @@ const withdrawResult = await invariant.withdrawSingleToken(admin, poolKey.tokenX console.log(withdrawResult) // get balance of a specific token after claiming position fees and print it -const adminBalanceAfterClaim = await tokenX.balanceOf(admin.addressRaw) +const adminBalanceAfterClaim = await GRC20.balanceOf(admin.addressRaw, token0Address) console.log(adminBalanceAfterClaim) ``` @@ -648,11 +658,9 @@ console.log(adminBalanceAfterClaim) The entrypoint facilitates the seamless transfer of positions between users. This functionality streamlines the process of reassigning ownership of a specific position to another account. The entrypoint takes two parameters: index of position to transfer, address of account to receive the position. ```typescript -const positionToTransfer = await invariant.getPosition(account.addressRaw, 0n) - -// Transfer position from account (signer) to receiver -await invariant.transferPosition(account, 0n, receiver.addressRaw) - +const positionToTransfer = await invariant.getPosition(admin.addressRaw, 0n) +// Transfer position from admin (signer) to receiver +await invariant.transferPosition(admin, 0n, receiver.addressRaw) // load received position const receiverPosition = await invariant.getPosition(receiver.addressRaw, 0n) @@ -684,21 +692,23 @@ console.log(receiverPosition) Position is removed from the protocol, and fees associated with that position are automatically claimed in the background. Here's a detailed description of the process: ```typescript - // fetch user balances before removal - const adminToken0BalanceBeforeRemove = await tokenX.balanceOf(admin.addressRaw) - const adminToken1BalanceBeforeRemove = await tokenY.balanceOf(admin.addressRaw) - console.log(adminToken0BalanceBeforeRemove, adminToken1BalanceBeforeRemove) - // remove position - const removePositionResult = await invariant.removePosition(admin, positionId) - console.log(removePositionResult) - - await invariant.withdrawTokenPair(admin, [poolKey.tokenX, null], [poolKey.tokenY, null]) - // get balance of a specific token after removing position - const adminToken0BalanceAfterRemove = await tokenX.balanceOf(admin.addressRaw) - const adminToken1BalanceAfterRemove = await tokenY.balanceOf(admin.addressRaw) - - // print balances - console.log(adminToken0BalanceAfterRemove, adminToken1BalanceAfterRemove) +// fetch user balances before removal +const adminToken0BalanceBeforeRemove = await GRC20.balanceOf(admin.addressRaw, token0Address) +const adminToken1BalanceBeforeRemove = await GRC20.balanceOf(admin.addressRaw, token1Address) +console.log(adminToken0BalanceBeforeRemove, adminToken1BalanceBeforeRemove) +// remove position + +const removePositionResult = await invariant.removePosition(admin, positionId) +console.log(removePositionResult) + +await invariant.withdrawTokenPair(admin, [poolKey.tokenX, null], [poolKey.tokenY, null]) + +// get balance of a specific token after removing position +const adminToken0BalanceAfterRemove = await GRC20.balanceOf(admin.addressRaw, token0Address) +const adminToken1BalanceAfterRemove = await GRC20.balanceOf(admin.addressRaw, token1Address) + +// print balances +console.log(adminToken0BalanceAfterRemove, adminToken1BalanceAfterRemove) ``` :::tip Output 999999999999999986060000000119n 999999999999999997937796254308n
diff --git a/docs/sidebars.js b/docs/sidebars.js index fddd5ff6..c004e29e 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -167,6 +167,7 @@ module.exports = { items: [ 'vara/installation', 'vara/overview', + 'vara/sdk', 'vara/types', 'vara/storage', 'vara/collections',