Skip to content

Commit

Permalink
CU-86a5jgc40-BS Swap - Add refund in SwapStatus type and separate get…
Browse files Browse the repository at this point in the history
…Status method
  • Loading branch information
hotequil committed Nov 11, 2024
1 parent 3f90007 commit fb76bd3
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@cityofzion/blockchain-service",
"comment": "Add refund in SwapStatus type and separate getStatus method",
"type": "minor"
}
],
"packageName": "@cityofzion/blockchain-service"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@cityofzion/bs-swap",
"comment": "Add refund in SwapStatus type and separate getStatus method",
"type": "minor"
}
],
"packageName": "@cityofzion/bs-swap"
}
10 changes: 7 additions & 3 deletions packages/blockchain-service/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,13 @@ export type SwapServiceSwapResult = {
}

export type SwapServiceStatusResponse = {
status: 'finished' | 'confirming' | 'exchanging' | 'failed'
transactionHashes: string[]
status: 'finished' | 'confirming' | 'exchanging' | 'failed' | 'refunded'
txFrom?: string
txTo?: string
}

export interface SwapServiceHelper {
getStatus(id: string): Promise<SwapServiceStatusResponse>
}

export interface SwapService<BSName extends string = string> {
Expand All @@ -300,5 +305,4 @@ export interface SwapService<BSName extends string = string> {
setAddressToReceive(address: string | null): Promise<void>
swap(): Promise<SwapServiceSwapResult>
calculateFee(): Promise<string>
getStatus(): Promise<SwapServiceStatusResponse>
}
28 changes: 14 additions & 14 deletions packages/bs-swap/src/__tests__/SimpleSwapService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ let amountToUse: SwapServiceLoadableValue<string>
let amountToUseMinMax: SwapServiceLoadableValue<SwapServiceMinMaxAmount>
let amountToReceive: SwapServiceLoadableValue<string>
let addressToReceive: SwapServiceValidateValue<string>
let accountToUse: SwapServiceLoadableValue<Account<'neo3'>>
let accountToUse: SwapServiceValidateValue<Account<'neo3'>>

describe('SimpleSwapService', () => {
beforeEach(async () => {
Expand All @@ -31,7 +31,7 @@ describe('SimpleSwapService', () => {
amountToUseMinMax = { loading: false, value: null }
amountToReceive = { loading: false, value: null }
addressToReceive = { loading: false, value: null, valid: null }
accountToUse = { loading: false, value: null }
accountToUse = { loading: false, value: null, valid: null }

blockchainServicesByName = {
neo3: new BSNeo3('neo3'),
Expand Down Expand Up @@ -110,7 +110,7 @@ describe('SimpleSwapService', () => {

expect(availableTokensToUse).toEqual({ loading: false, value: expect.any(Array) })
expect(tokenToUse).toEqual({ loading: false, value: null })
expect(accountToUse).toEqual({ loading: false, value: null })
expect(accountToUse).toEqual({ loading: false, value: null, valid: null })
expect(amountToUse).toEqual({ loading: false, value: null })
expect(amountToReceive).toEqual({ loading: false, value: null })
expect(tokenToReceive).toEqual({ loading: false, value: null })
Expand All @@ -122,7 +122,7 @@ describe('SimpleSwapService', () => {
it('Should be able to set the token to use', async () => {
await simpleSwapService.init()

const token = availableTokensToUse.value![0]
const token = availableTokensToUse.value![1]

await simpleSwapService.setTokenToUse(token)

Expand All @@ -147,7 +147,7 @@ describe('SimpleSwapService', () => {

it('Should not be able to set the account to use if account blockchain is different of token to use blockchain', async () => {
await simpleSwapService.init()
await simpleSwapService.setTokenToUse(availableTokensToUse.value![0])
await simpleSwapService.setTokenToUse(availableTokensToUse.value![1])

const account = blockchainServicesByName.neo3.generateAccountFromKey(process.env.TEST_PRIVATE_KEY as string)
account.blockchain = 'NONEXISTENT' as any
Expand All @@ -159,7 +159,7 @@ describe('SimpleSwapService', () => {
it('Should be able to set the account to use to null', async () => {
await simpleSwapService.init()

const token = availableTokensToUse.value![0]
const token = availableTokensToUse.value![1]

await simpleSwapService.setTokenToUse(token)
await simpleSwapService.setAccountToUse(null)
Expand All @@ -177,7 +177,7 @@ describe('SimpleSwapService', () => {

it('Should be able to set the account to use', async () => {
await simpleSwapService.init()
const token = availableTokensToUse.value![0]
const token = availableTokensToUse.value![1]
await simpleSwapService.setTokenToUse(token)

expect(accountToUse).toEqual({ loading: false, value: null, valid: null })
Expand All @@ -198,7 +198,7 @@ describe('SimpleSwapService', () => {

it('Should be able to set the amount to use', async () => {
await simpleSwapService.init()
const token = availableTokensToUse.value![0]
const token = availableTokensToUse.value![1]
await simpleSwapService.setTokenToUse(token)

const account = blockchainServicesByName.neo3.generateAccountFromKey(process.env.TEST_PRIVATE_KEY as string)
Expand Down Expand Up @@ -231,7 +231,7 @@ describe('SimpleSwapService', () => {

it("Should not be able to set the token to receive if it's not in the available tokens to receive", async () => {
await simpleSwapService.init()
const token = availableTokensToUse.value![0]
const token = availableTokensToUse.value![1]

await simpleSwapService.setTokenToUse(token)

Expand All @@ -242,7 +242,7 @@ describe('SimpleSwapService', () => {

it('Should be able to set the token to receive to null', async () => {
await simpleSwapService.init()
const token = availableTokensToUse.value![0]
const token = availableTokensToUse.value![1]
await simpleSwapService.setTokenToUse(token)

const account = blockchainServicesByName.neo3.generateAccountFromKey(process.env.TEST_PRIVATE_KEY as string)
Expand All @@ -263,13 +263,13 @@ describe('SimpleSwapService', () => {

it('Should be able to set the token to receive', async () => {
await simpleSwapService.init()
const tokenUse = availableTokensToUse.value![0]
const tokenUse = availableTokensToUse.value![1]
await simpleSwapService.setTokenToUse(tokenUse)

const account = blockchainServicesByName.neo3.generateAccountFromKey(process.env.TEST_PRIVATE_KEY as string)
await simpleSwapService.setAccountToUse(account)

const tokenReceive = availableTokensToReceive.value![1]
const tokenReceive = availableTokensToReceive.value![0]
await simpleSwapService.setTokenToReceive(tokenReceive)

expect(tokenToUse).toEqual({ loading: false, value: tokenUse })
Expand All @@ -285,7 +285,7 @@ describe('SimpleSwapService', () => {

it('Should be able to set an invalid address', async () => {
await simpleSwapService.init()
const tokenUse = availableTokensToUse.value![0]
const tokenUse = availableTokensToUse.value![1]
await simpleSwapService.setTokenToUse(tokenUse)

const account = blockchainServicesByName.neo3.generateAccountFromKey(process.env.TEST_PRIVATE_KEY as string)
Expand Down Expand Up @@ -328,5 +328,5 @@ describe('SimpleSwapService', () => {
expect(amountToReceive).toEqual({ loading: false, value: expect.any(String) })
expect(addressToReceive).toEqual({ loading: false, value: account.address, valid: true })
expect(amountToUseMinMax).toEqual({ loading: false, value: expect.objectContaining({ min: expect.any(String) }) })
}, 10000)
}, 20000)
})
26 changes: 15 additions & 11 deletions packages/bs-swap/src/apis/SimpleSwapApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { BlockchainService } from '@cityofzion/blockchain-service'
import axios, { AxiosInstance } from 'axios'
import {
SimpleSwapApiCreateExchangeResponse,
Expand All @@ -15,23 +14,25 @@ import {
export class SimpleSwapApi<BSName extends string = string> {
#axios: AxiosInstance
#allCurrenciesMap: Map<string, SimpleSwapApiCurrency<BSName>> = new Map()
#chainsByServiceNameEntries: [BSName, string[]][]
#blockchainServicesByName: Record<BSName, BlockchainService<BSName>>

constructor({ apiKey, blockchainServicesByName, chainsByServiceName }: SimpleSwapServiceInitParams<BSName>) {
constructor(apiKey: string) {
this.#axios = axios.create({ baseURL: 'https://api.simpleswap.io/v3', headers: { 'X-API-KEY': apiKey } })
this.#chainsByServiceNameEntries = Object.entries(chainsByServiceName) as [BSName, string[]][]
this.#blockchainServicesByName = blockchainServicesByName
}

#getTokenFromCurrency(currency: SimpleSwapApiCurrencyResponse): SimpleSwapApiCurrency<BSName> | undefined {
#getTokenFromCurrency(
currency: SimpleSwapApiCurrencyResponse,
options: Omit<SimpleSwapServiceInitParams<BSName>, 'apiKey'>
): SimpleSwapApiCurrency<BSName> | undefined {
if (!currency.ticker || !currency.network || !currency.image || !currency.name || !currency.validationAddress) {
return
}

const chainsByServiceNameEntry = this.#chainsByServiceNameEntries.find(([_serviceName, chains]) =>
const chainsByServiceNameEntries = Object.entries(options.chainsByServiceName) as [BSName, string[]][]

const chainsByServiceNameEntry = chainsByServiceNameEntries.find(([_serviceName, chains]) =>
chains.includes(currency.network!)
)

let hash = currency.contractAddress ?? undefined
let decimals: number | undefined
let name = currency.name
Expand All @@ -42,7 +43,7 @@ export class SimpleSwapApi<BSName extends string = string> {
blockchain = chainsByServiceNameEntry[0]

if (!hash) {
const token = this.#blockchainServicesByName[blockchain].tokens.find(
const token = options.blockchainServicesByName[blockchain].tokens.find(
token => currency.ticker?.toLowerCase().startsWith(token.symbol.toLowerCase())
)

Expand All @@ -69,7 +70,9 @@ export class SimpleSwapApi<BSName extends string = string> {
}
}

async getCurrencies() {
async getCurrencies(
options: Omit<SimpleSwapServiceInitParams<BSName>, 'apiKey'>
): Promise<SimpleSwapApiCurrency<BSName>[]> {
if (this.#allCurrenciesMap.size) {
return Array.from(this.#allCurrenciesMap.values())
}
Expand All @@ -79,7 +82,7 @@ export class SimpleSwapApi<BSName extends string = string> {
const tokens: SimpleSwapApiCurrency<BSName>[] = []

response.data.result.forEach(currency => {
const token = this.#getTokenFromCurrency(currency)
const token = this.#getTokenFromCurrency(currency, options)
if (!token) return

this.#allCurrenciesMap.set(`${token.ticker}:${token.network}`, token)
Expand Down Expand Up @@ -128,6 +131,7 @@ export class SimpleSwapApi<BSName extends string = string> {
amount,
},
})

return response.data.result.estimatedAmount
}

Expand Down
34 changes: 34 additions & 0 deletions packages/bs-swap/src/helpers/SimpleSwapServiceHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { SwapServiceHelper, SwapServiceStatusResponse } from '@cityofzion/blockchain-service'
import { SimpleSwapApi } from '../apis/SimpleSwapApi'

export class SimpleSwapServiceHelper<BSName extends string = string> implements SwapServiceHelper {
#api: SimpleSwapApi<BSName>

constructor(apiKey: string) {
this.#api = new SimpleSwapApi(apiKey)
}

async getStatus(id: string): Promise<SwapServiceStatusResponse> {
const response = await this.#api.getExchange(id)

const statusBySimpleSwapStatus: Record<string, SwapServiceStatusResponse['status']> = {
waiting: 'confirming',
confirming: 'confirming',
exchanging: 'exchanging',
sending: 'exchanging',
verifying: 'exchanging',
finished: 'finished',
expired: 'failed',
failed: 'failed',
refunded: 'refunded',
}

const status = statusBySimpleSwapStatus[response.status]

return {
status,
txFrom: response.txFrom,
txTo: response.txTo,
}
}
}
57 changes: 14 additions & 43 deletions packages/bs-swap/src/services/SimpleSwapService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
SwapServiceEvents,
SwapServiceLoadableValue,
SwapServiceMinMaxAmount,
SwapServiceStatusResponse,
SwapServiceSwapResult,
SwapServiceToken,
SwapServiceValidateValue,
Expand All @@ -24,9 +23,7 @@ export class SimpleSwapService<BSName extends string = string> implements SwapSe

#api: SimpleSwapApi<BSName>
#blockchainServicesByName: Record<BSName, BlockchainService<BSName>>

#internalExchangeId: string | undefined = undefined
#internalTransactionHash: string | undefined = undefined
#chainsByServiceName: Partial<Record<BSName, string[]>>

#internalAvailableTokensToUse: SwapServiceLoadableValue<SimpleSwapApiCurrency<BSName>[]> = {
loading: true,
Expand All @@ -46,8 +43,9 @@ export class SimpleSwapService<BSName extends string = string> implements SwapSe

constructor(params: SimpleSwapServiceInitParams<BSName>) {
this.eventEmitter = new EventEmitter() as TypedEmitter<SwapServiceEvents>
this.#api = new SimpleSwapApi(params)
this.#api = new SimpleSwapApi(params.apiKey)
this.#blockchainServicesByName = params.blockchainServicesByName
this.#chainsByServiceName = params.chainsByServiceName
}

get #availableTokensToUse(): SwapServiceLoadableValue<SimpleSwapApiCurrency<BSName>[]> {
Expand Down Expand Up @@ -176,12 +174,14 @@ export class SimpleSwapService<BSName extends string = string> implements SwapSe
}

if (shouldRecalculateAmountToReceive) {
const estimate = await this.#api.getEstimate(
this.#tokenToUse.value,
this.#tokenToReceive.value!,
this.#amountToUse.value!
)

this.#amountToReceive = {
value: await this.#api.getEstimate(
this.#tokenToUse.value,
this.#tokenToReceive.value!,
this.#amountToUse.value!
),
value: estimate,
}
}
}
Expand All @@ -194,7 +194,10 @@ export class SimpleSwapService<BSName extends string = string> implements SwapSe
}

async init() {
const tokens = await this.#api.getCurrencies()
const tokens = await this.#api.getCurrencies({
blockchainServicesByName: this.#blockchainServicesByName,
chainsByServiceName: this.#chainsByServiceName,
})
this.#availableTokensToUse = { loading: false, value: tokens }
}

Expand Down Expand Up @@ -289,9 +292,6 @@ export class SimpleSwapService<BSName extends string = string> implements SwapSe
],
})

this.#internalExchangeId = id
this.#internalTransactionHash = transactionHash

return {
id,
// SimpleSwap always make 2 transactions
Expand Down Expand Up @@ -331,33 +331,4 @@ export class SimpleSwapService<BSName extends string = string> implements SwapSe
],
})
}

async getStatus(): Promise<SwapServiceStatusResponse> {
if (!this.#internalExchangeId || !this.#internalTransactionHash) throw new Error('You need to execute a swap first')

const response = await this.#api.getExchange(this.#internalExchangeId)

const transactionHashes: string[] = [this.#internalTransactionHash]

if (response.txTo) transactionHashes.push(response.txTo)

const statusBySimpleSwapStatus: Record<string, SwapServiceStatusResponse['status']> = {
waiting: 'confirming',
confirming: 'confirming',
exchanging: 'exchanging',
sending: 'exchanging',
verifying: 'exchanging',
finished: 'finished',
expired: 'failed',
failed: 'failed',
refunded: 'failed',
}

const status = statusBySimpleSwapStatus[response.status]

return {
status,
transactionHashes: transactionHashes,
}
}
}
4 changes: 2 additions & 2 deletions packages/bs-swap/src/types/simpleSwap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export type SimpleSwapApiCreateExchangeResponse = {
export type SimpleSwapApiGetExchangeResponse = {
result: {
status: string
txFrom: string
txTo: string
txFrom?: string
txTo?: string
}
}

0 comments on commit fb76bd3

Please sign in to comment.