---
# System prepended metadata

title: User Action Types

---

# User Action Types



* LiquidityRemoved is not the same for LBP and XYK, because LBP pool is destroyed after.

```
enum status = {
    unapproved
    isBroadcast
    isInBlock
    isFinalized
}

enum UserActionType {
    feeAssetSet# multiTransactionPayment set currency
    transfer
    sell
    buy
    addLiquidity # for now XYK
    removeLiquidity # for now XYK
    XCM # cross chain transfer
}

type UserAction @entity {
    # blockNumber-index-partialBlockHash
    # eg. "0000000057-000002-28ed9
    ID: ID!
    hash: string!
    status: status!
    account: address!
    action: UserAction!
    detail: UserActionDetail
    paraChainBlockHeight: bigint # this will be added after it is inBlock
}

union UserActionDetail = SellActionDetail | BuyActionDetail | LiquidityAddedActionDetail | LiquidityRemovedActionDetail
```

## UI-Processor strategy
#### 1. create UserAction (before approving tx in UI)
```
userAction = {
    id: undefined
    hash: extrinsicHash # tx.hash.toHex()
    status: unapproved
    account: address
    action: according to action
    detail: see below,
    # following is not in processor
    currentBlockNumber: number,
    extrinsicStatus: pending
}
```
##### Details for step 1

```
sellActionDetail {
    poolId: address!
    assetIn: bigint!
    assetOut: bigint!
    assetInAmount: bigint!
}
```

```
buyActionDetail {
    poolId: address!
    assetOut: bigint!
    assetIn: bigint!
    assetOutAmount: bigint!
}
```

```
liquidityAddedActionDetail {
    poolId: address!
    assetA: bigint!
    assetB: bigint!
    assetAAmount: bigint!
    assetBAmount: bigint!
}
```

```
liquidityRemovedActionDetail {
    poolId: address!
    assetA: bigint!
    assetB: bigint!
    assetAAmount: bigint!
    assetBAmount: bigint!
}
```

```
transferActionDetail {
    to: address!
    asset: bigint!
    amount: bigint!
}
```

#### 2. Sign and broadcast
Subscribe with polkadot.js to updates when signing and broadcasting transaction.

```
userAction = {
    ...userAction
    status: isBroadcast,
}
```

Additionally save `currentBlockNumber` & `tx hash` for userAction to be used to handle not "happy path" in section [exceptional cases](#Exceptional-cases). 


#### 3. Add id and blockNumber
```
userAction = {
    ...userAction
    id: id
    blockHeight: paraChainBlockHeight
    extrinsicStatus: succeeded | failed
}
```


```
// tx is the extrinsic transaction object 
if (status.isInBlock) {    
    const transactionHash = result.status.hash.toString());
  
    const blockHash = status.asInBlock.toString();
    const signedBlock = await api.rpc.chain.getBlock(blockHash);
    const paraChainBlockHeight = signedBlock.block.header.number.toBigInt();
    
    const index = signedBlock.block.extrinsics.forEach((ex, index) => {
        if(ex.hash.toHex() === tx.hash.toHex()) return index
    });       
    // create ID=blockNumber-index-partialBlockHash
}
```

```
// example to get extrinsicStatus

_events.forEach(({ event: _event, phase }) => {
    if (api.events.system.ExtrinsicSuccess.is(_event)) {
        const [dispatchInfo] = _event.data;

        console.log(
            `${_event.section}.${
                _event.method
            }:: ExtrinsicSuccess:: ${dispatchInfo.toHuman()}`
        );
    } else if (
        api.events.system.ExtrinsicFailed.is(_event)
    ) {
        // extract the data for this event
        const [dispatchError, dispatchInfo] =
            _event.data;
        let errorInfo;

        // decode the error
        if (dispatchError.isModule) {
            // for module errors, we have the section indexed, lookup
            // (For specific known errors, we can also do a check against the
            // api.errors.<module>.<ErrorName>.is(dispatchError.asModule) guard)
            const decoded = api.registry.findMetaError(
                dispatchError.asModule
            );

            errorInfo = `${decoded.section}.${decoded.name}`;
        } else {
            // Other, CannotLookup, BadOrigin, no extra info
            errorInfo = dispatchError.toString();
        }

        console.log(
            `${_event.section}.${_event.method}:: ExtrinsicFailed:: ${errorInfo}`
        );
    }
});
```

#### 4. Finalize userAction.id and fetch from processor userAction based on id

```
// update userAction.id if necessary
if (status.isFinalized) {
    if(status.asFinalized.toHex() === status.asInBlock.toHex()) { skip } else {
        // create new ID the same way as "isInBlock" step
    }
}
...
// check whether extrinsic status has changed (consider reorg, new tx order etc...)
```

Fetch from processor with finalized userAction.id and replace partial userAction held in UI with the finalized entry from processor.
#### Exceptional cases

##### User refreshes between status.isBroadcast and status.isInBlock

Impatient user refreshes shortly after broadcasting and closes subscription for extrinsic. That's why we need to search with the extrinsic hash in various signed blocks until we find the extrinsic. Therefore **step 3** changes to the following code block and needs to be repeated for incrementing currentBlockNumber until id is created.

```
// take blockNumber from step 2 and repeat process until id can be created
const blockNumber = currentBlockNumber 
const blockHash = await api.rpc.chain.getBlockHash(blockNumber);
const signedBlock = await api.rpc.chain.getBlock(
    blockHash
);

// blockNumber
const paraChainBlockHeight = signedBlock.block.header.number.toBigInt();

signedBlock.block.extrinsics.forEach((ex, index) => {
    
const index = signedBlock.block.extrinsics.forEach((ex, index) => {
    if(ex.hash.toHex() === tx.hash.toHex()) return index
});        
// create ID=blockNumber-index-partialBlockHash
```

##### Extrinsic failed

Retrieve 

## userActionDetails

### Sell
LBP: [who, asset_in, asset_out, amount, sale_price, fee_asset, fee_amount]
XYK: [who, asset in, asset out, amount, sale price, fee asset, fee amount, pool account id]


```
sellActionDetail {
    poolId: address!
    assetIn: bigint!
    assetOut: bigint!
    assetInAmount: bigint!
    assetOutAmount: bigint!
    sellPrice: bigint!
    feeAsset: bigint!
    feeBalance: bigint!
}
```

### Buy
LBP: [who, asset_out, asset_in, amount, buy_price, fee_asset, fee_amount, **no pool id***]
XYK: [who, asset out, asset in, amount, buy price, fee asset, fee amount, **poolId****]
*TODO: look into why no poolId provided - maybe use extrinsic info
**TODO: update comment in source code because docs are inconsistent

```
buyActionDetail {
    poolId: address!
    assetOut: bigint!
    assetIn: bigint!
    assetOutAmount: bigint!
    assetInAmount: bigint!
    buyPrice: bigint!
    feeAsset: bigint!
    feeBalance: bigint!
}
```

### Liquidity Added
LBP: [who, asset_a, asset_b, amount_a, amount_b]
XYK: [who, asset a, asset b, amount a, amount b]

```
liquidityAddedActionDetail {
    poolId: address!
    assetA: bigint!
    assetB: bigint!
    assetAAmount: bigint!
    assetBAmount: bigint!
}
```


### Liquidity Removed
XYK
skip LBP

```
liquidityRemovedActionDetail {
    poolId: address!
    assetA: bigint!
    assetB: bigint!
    assetAAmount: bigint!
    assetBAmount: bigint!
}
```

## Notes

### Unapproved status in queue
![](https://i.imgur.com/tuiFAhK.png)

    
![](https://i.imgur.com/0quXxSq.png)




Available Polkadot js API status updates
```
Status
  isFuture: [Getter],
  isReady: [Getter],
  isBroadcast: [Getter], <- use
  isInBlock: [Getter], <- use
  isRetracted: [Getter],
  isFinalityTimeout: [Getter],
  isFinalized: [Getter], <- use
  isUsurped: [Getter], <- research
  isDropped: [Getter],
  isInvalid: [Getter],
```

```
# example to get the block hash
...
const unsub = await api.tx.balances
  .transfer(recipient, 12345)
  .signAndSend(signer, (result) => {
    console.log(`Current status is ${result.status}`);

    if (result.status.isInBlock) {
      console.log(`Transaction included at blockHash ${result.status.asInBlock}`);
    } else if (result.status.isFinalized) {
      console.log(`Transaction finalized at blockHash ${result.status.asFinalized}`);
      unsub();
    }
  });
```



---

## Source code
```
/// Pool was created by the `CreatePool` origin. [pool_id, pool_data`
PoolCreated(
    PoolId<T>, 
    Pool<T::AccountId, T::BlockNumber>
),

/// Pool data were updated. [pool_id, pool_data]
PoolUpdated(
    PoolId<T>, 
    Pool<T::AccountId, T::BlockNumber>
),

/// New liquidity was provided to the pool. [who, asset_a, asset_b, amount_a, amount_b]
LiquidityAdded(
    T::AccountId, 
    AssetId, 
    AssetId, 
    BalanceOf<T>, 
    BalanceOf<T>
),

/// Liquidity was removed from the pool and the pool was destroyed. [who, asset_a, asset_b, amount_a, amount_b]
LiquidityRemoved(T::AccountId, AssetId, AssetId, BalanceOf<T>, BalanceOf<T>),

// Sale executed. [who, asset_in, asset_out, amount, sale_price, fee_asset, fee_amount]
SellExecuted(
    T::AccountId,
    AssetId,
    AssetId,
    BalanceOf<T>,
    BalanceOf<T>,
    AssetId,
    BalanceOf<T>,
),

/// Purchase executed. [who, asset_out, asset_in, amount, buy_price, fee_asset, fee_amount]
BuyExecuted(
    T::AccountId,
    AssetId,
    AssetId,
    BalanceOf<T>,
    BalanceOf<T>,
    AssetId,
    BalanceOf<T>,
),
```

# XYK

```
/// New liquidity was provided to the pool. [who, asset a, asset b, amount a, amount b]
LiquidityAdded(
    T::AccountId, 
    AssetId, 
    AssetId, 
    Balance, 
    Balance
),

/// Liquidity was removed from the pool. [who, asset a, asset b, shares]
LiquidityRemoved(
    T::AccountId, 
    AssetId, 
    AssetId, 
    Balance
),

/// Pool was created. [who, asset a, asset b, initial shares amount, share token, pool account id]
PoolCreated(
    T::AccountId, 
    AssetId, 
    AssetId, 
    Balance, 
    AssetId, 
    T::AccountId
),

/// Pool was destroyed. [who, asset a, asset b, share token, pool account id]
PoolDestroyed(
    T::AccountId, 
    AssetId, 
    AssetId, 
    AssetId, 
    T::AccountId),

/// Asset sale executed. [who, asset in, asset out, amount, sale price, fee asset, fee amount, pool account id]
SellExecuted(
    T::AccountId,
    AssetId,
    AssetId,
    Balance,
    Balance,
    AssetId,
    Balance,
    T::AccountId,
),

/// Asset purchase executed. [who, asset out, asset in, amount, buy price, fee asset, fee amount]
BuyExecuted(
    T::AccountId,
    AssetId,
    AssetId,
    Balance,
    Balance,
    AssetId,
    Balance,
    T::AccountId,
),
```