# 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


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,
),
```