Skip to content

Commit 3ea592e

Browse files
[BANK-5155] Add CPS Trade APIs (#397)
Duplicate of #396
1 parent 39ed5e2 commit 3ea592e

File tree

12 files changed

+1038
-14
lines changed

12 files changed

+1038
-14
lines changed

layouts/default.vue

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,32 @@
214214
</v-list-item-content>
215215
</v-list-item>
216216
</v-list-group>
217+
218+
<v-list-group>
219+
<template #activator>
220+
<v-list-item-title>CPS Trade API</v-list-item-title>
221+
</template>
222+
223+
<v-list-item to="/debug/cps" router exact>
224+
<v-list-item-content>
225+
<v-list-item-title class="list-items pl-2">
226+
Overview
227+
</v-list-item-title>
228+
</v-list-item-content>
229+
</v-list-item>
230+
231+
<v-list-item
232+
v-for="(item, i) in cpsTradesLinks"
233+
:key="`cpsTradesLinks-${i}`"
234+
:to="item.to"
235+
router
236+
exact
237+
>
238+
<v-list-item-content>
239+
<v-list-item-title class="list-items pl-2" v-text="item.title" />
240+
</v-list-item-content>
241+
</v-list-item>
242+
</v-list-group>
217243
</v-list>
218244
</v-navigation-drawer>
219245
<v-app-bar clipped-left fixed app dark color="primary" dense>
@@ -650,6 +676,29 @@ export default class DefaultLayoutsClass extends Vue {
650676
},
651677
]
652678
679+
cpsTradesLinks = [
680+
{
681+
title: 'POST /cps/quotes',
682+
to: '/debug/cps/quote',
683+
},
684+
{
685+
title: 'POST /cps/trades',
686+
to: '/debug/cps/create',
687+
},
688+
{
689+
title: 'GET /cps/trades',
690+
to: '/debug/cps/fetch',
691+
},
692+
{
693+
title: 'GET /cps/trades/{id}',
694+
to: '/debug/cps/details',
695+
},
696+
{
697+
title: 'POST /cps/signatures',
698+
to: '/debug/cps/signature',
699+
},
700+
]
701+
653702
miniVariant = false
654703
right = true
655704
showRightDrawer = false

lib/cpsTradesApi.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import get from 'lodash/get'
2+
import axios from 'axios'
3+
4+
import { getAPIHostname } from './apiTarget'
5+
6+
export interface CreateCpsQuotePayload {
7+
from: {
8+
amount?: number
9+
currency: string
10+
}
11+
to: {
12+
amount?: number
13+
currency: string
14+
}
15+
}
16+
17+
export interface CreateCpsTradePayload {
18+
idempotencyKey: string
19+
quoteId: string
20+
}
21+
22+
export interface Consideration {
23+
quoteId: string
24+
base: string
25+
quote: string
26+
quoteAmount: number
27+
baseAmount: number
28+
maturity: number
29+
}
30+
31+
export interface PiFXTraderDetails {
32+
recipient: string
33+
deadline: number
34+
nonce: number
35+
consideration: Consideration
36+
}
37+
38+
export interface CreatePiFXSignaturePayload {
39+
tradeId: string
40+
type: string
41+
address: string
42+
details: PiFXTraderDetails
43+
signature: string
44+
}
45+
46+
const instance = axios.create({
47+
baseURL: getAPIHostname(),
48+
})
49+
50+
const CPS_TRADES_PATH = '/v1/exchange/cps/trades'
51+
const CPS_QUOTES_PATH = '/v1/exchange/cps/quotes'
52+
const CPS_SIGNATURES_PATH = '/v1/exchange/cps/signatures'
53+
54+
/**
55+
* Global error handler:
56+
* Intercepts all axios reponses and maps
57+
* to errorHandler object
58+
*/
59+
instance.interceptors.response.use(
60+
function (response) {
61+
if (get(response, 'data.data')) {
62+
return response.data.data
63+
}
64+
return response
65+
},
66+
function (error) {
67+
let response = get(error, 'response')
68+
if (!response) {
69+
response = error.toJSON()
70+
}
71+
return Promise.reject(response)
72+
}
73+
)
74+
75+
/** Returns the axios instance */
76+
function getInstance() {
77+
return instance
78+
}
79+
80+
/**
81+
* Create CPS Quote
82+
*/
83+
function createQuote(payload: CreateCpsQuotePayload) {
84+
if (!payload.from.amount) {
85+
delete payload.from.amount
86+
}
87+
if (!payload.to.amount) {
88+
delete payload.to.amount
89+
}
90+
91+
return instance.post(CPS_QUOTES_PATH, payload)
92+
}
93+
94+
/**
95+
* Create CPS Trade
96+
*/
97+
function createTrade(payload: CreateCpsTradePayload) {
98+
return instance.post(CPS_TRADES_PATH, payload)
99+
}
100+
101+
/**
102+
* Get CPS Trades
103+
*/
104+
function getTrades() {
105+
return instance.get(CPS_TRADES_PATH)
106+
}
107+
108+
/**
109+
* Get CPS Trade
110+
*/
111+
function getTrade(tradeId: string) {
112+
const url = `${CPS_TRADES_PATH}/${tradeId}`
113+
114+
return instance.get(url)
115+
}
116+
117+
/**
118+
* Register CPS Signature
119+
*/
120+
function registerSignature(payload: CreatePiFXSignaturePayload) {
121+
return instance.post(CPS_SIGNATURES_PATH, payload)
122+
}
123+
124+
export default {
125+
getInstance,
126+
createQuote,
127+
createTrade,
128+
getTrades,
129+
getTrade,
130+
registerSignature,
131+
}

nuxt.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ export default {
5252
'~/plugins/walletsApi',
5353
'~/plugins/paymentIntentsApi',
5454
'~/plugins/mocksApi',
55+
'~/plugins/tradesApi',
56+
'~/plugins/cpsTradesApi',
5557
'~/plugins/businessAccount/addressesApi',
5658
'~/plugins/businessAccount/balancesApi',
5759
'~/plugins/businessAccount/bankAccountsApi',

pages/debug/cps/create.vue

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<template>
2+
<v-layout>
3+
<v-row>
4+
<v-col cols="12" md="4">
5+
<v-form v-model="validForm">
6+
<v-text-field
7+
v-model="formData.quoteId"
8+
:rules="[required]"
9+
hint="ID of CPS FX quote to trade on"
10+
label="Quote ID"
11+
/>
12+
<v-btn
13+
depressed
14+
class="mb-7"
15+
color="primary"
16+
:loading="loading"
17+
:disabled="!validForm || loading"
18+
@click.prevent="makeApiCall"
19+
>
20+
Make api call
21+
</v-btn>
22+
</v-form>
23+
</v-col>
24+
<v-col cols="12" md="8">
25+
<RequestInfo
26+
:url="requestUrl"
27+
:payload="payload"
28+
:response="response"
29+
/>
30+
</v-col>
31+
</v-row>
32+
<ErrorSheet
33+
:error="error"
34+
:show-error="showError"
35+
@onChange="onErrorSheetClosed"
36+
/>
37+
</v-layout>
38+
</template>
39+
40+
<script lang="ts">
41+
import { Component, Vue } from 'nuxt-property-decorator'
42+
import { mapGetters } from 'vuex'
43+
import { v4 as uuidv4 } from 'uuid'
44+
import RequestInfo from '@/components/RequestInfo.vue'
45+
import ErrorSheet from '@/components/ErrorSheet.vue'
46+
import { CreateCpsTradePayload } from '~/lib/cpsTradesApi'
47+
48+
@Component({
49+
components: {
50+
RequestInfo,
51+
ErrorSheet,
52+
},
53+
computed: {
54+
...mapGetters({
55+
payload: 'getRequestPayload',
56+
response: 'getRequestResponse',
57+
requestUrl: 'getRequestUrl',
58+
}),
59+
},
60+
})
61+
export default class CreateCpsTradeClass extends Vue {
62+
validForm: boolean = false
63+
formData = {
64+
quoteId: '',
65+
}
66+
67+
required = (v: string) => !!v || 'Field is required'
68+
error = {}
69+
loading = false
70+
showError = false
71+
72+
onErrorSheetClosed() {
73+
this.error = {}
74+
this.showError = false
75+
}
76+
77+
async makeApiCall() {
78+
this.loading = true
79+
80+
const payload: CreateCpsTradePayload = {
81+
idempotencyKey: uuidv4(),
82+
quoteId: this.formData.quoteId,
83+
}
84+
85+
try {
86+
await this.$cpsTradesApi.createTrade(payload)
87+
} catch (error) {
88+
this.error = error
89+
this.showError = true
90+
} finally {
91+
this.loading = false
92+
}
93+
}
94+
}
95+
</script>

pages/debug/cps/details.vue

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<template>
2+
<v-layout>
3+
<v-row>
4+
<v-col cols="12" md="4">
5+
<v-form v-model="validForm">
6+
<v-text-field
7+
v-model="formData.tradeId"
8+
:rules="[required]"
9+
label="CPS Trade ID"
10+
/>
11+
<v-btn
12+
depressed
13+
class="mb-7"
14+
color="primary"
15+
:loading="loading"
16+
:disabled="!validForm || loading"
17+
@click.prevent="makeApiCall"
18+
>
19+
Make api call
20+
</v-btn>
21+
</v-form>
22+
</v-col>
23+
<v-col cols="12" md="8">
24+
<RequestInfo
25+
:url="requestUrl"
26+
:payload="payload"
27+
:response="response"
28+
/>
29+
</v-col>
30+
</v-row>
31+
<ErrorSheet
32+
:error="error"
33+
:show-error="showError"
34+
@onChange="onErrorSheetClosed"
35+
/>
36+
</v-layout>
37+
</template>
38+
39+
<script lang="ts">
40+
import { Component, Vue } from 'nuxt-property-decorator'
41+
import { mapGetters } from 'vuex'
42+
import RequestInfo from '@/components/RequestInfo.vue'
43+
import ErrorSheet from '@/components/ErrorSheet.vue'
44+
45+
@Component({
46+
components: {
47+
RequestInfo,
48+
ErrorSheet,
49+
},
50+
computed: {
51+
...mapGetters({
52+
payload: 'getRequestPayload',
53+
response: 'getRequestResponse',
54+
requestUrl: 'getRequestUrl',
55+
isMarketplace: 'isMarketplace',
56+
}),
57+
},
58+
})
59+
export default class GetCpsTradeDetailsClass extends Vue {
60+
validForm: boolean = false
61+
formData = {
62+
tradeId: '',
63+
}
64+
65+
required = (v: string) => !!v || 'Field is required'
66+
error = {}
67+
loading = false
68+
showError = false
69+
70+
onErrorSheetClosed() {
71+
this.error = {}
72+
this.showError = false
73+
}
74+
75+
async makeApiCall() {
76+
this.loading = true
77+
78+
try {
79+
await this.$cpsTradesApi.getTrade(this.formData.tradeId)
80+
} catch (error) {
81+
this.error = error
82+
this.showError = true
83+
} finally {
84+
this.loading = false
85+
}
86+
}
87+
}
88+
</script>

0 commit comments

Comments
 (0)