Skip to content

Commit

Permalink
feat: Introduce FIAT Gateway (#566)
Browse files Browse the repository at this point in the history
* feat: wip

* feat: fix ESLint issues

* feat: remove old unused import

* test: fix tests

* feat: fix getAuthDapp select

* feat: new logic to get identity

* feat: fix the marketplaceAPI wert signature call

* test: add tests for the new gateway actions

* test: fix tests

* test: add mock for Request

* feat: some small refactors
  • Loading branch information
juanmahidalgo authored Jan 22, 2024
1 parent 29e83fd commit c08e7ff
Show file tree
Hide file tree
Showing 12 changed files with 772 additions and 241 deletions.
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
"@0xsequence/relayer": "^0.25.1",
"@dcl/crypto": "^3.3.1",
"@dcl/schemas": "^9.10.0",
"@dcl/single-sign-on-client": "^0.1.0",
"@dcl/ui-env": "^1.4.0",
"@transak/transak-sdk": "^1.0.31",
"@types/flat": "0.0.28",
"@well-known-components/fetch-component": "^2.0.1",
"@wert-io/widget-initializer": "^5.2.0",
"axios": "^0.21.1",
"date-fns": "^1.29.0",
"dcl-catalyst-client": "^21.1.0",
Expand Down
24 changes: 24 additions & 0 deletions src/lib/marketplaceApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { AuthIdentity } from 'decentraland-crypto-fetch'
import { WertMessage } from '../modules/gateway/types'
import { BaseClient } from './BaseClient'

export class MarketplaceAPI extends BaseClient {
async signWertMessage(
message: WertMessage,
identity: AuthIdentity
): Promise<string> {
try {
const response = await this.fetch<string>('/v1/wert/sign', {
method: 'POST',
identity,
body: JSON.stringify(message),
headers: {
'Content-Type': 'application/json'
}
})
return response
} catch (error) {
throw new Error((error as Error).message)
}
}
}
59 changes: 57 additions & 2 deletions src/modules/gateway/actions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,20 @@ import {
POLL_PURCHASE_STATUS_REQUEST,
POLL_PURCHASE_STATUS_SUCCESS,
setPurchase,
SET_PURCHASE
SET_PURCHASE,
openFiatGatewayWidgetRequest,
OPEN_FIAT_GATEWAY_WIDGET_REQUEST,
openFiatGatewayWidgetSuccess,
OPEN_FIAT_GATEWAY_WIDGET_SUCCESS,
openFiatGatewayWidgetFailure,
OPEN_FIAT_GATEWAY_WIDGET_FAILURE
} from './actions'
import { Purchase, PurchaseStatus } from './types'
import {
FiatGateway,
FiatGatewayOptions,
Purchase,
PurchaseStatus
} from './types'

jest.mock('../../lib/eth')

Expand Down Expand Up @@ -261,3 +272,47 @@ describe('when creating the action that signals failure in the poll purchase sta
})
})
})

describe('when creating the action that signals the start of the fiat gateway widget request', () => {
let gateway: FiatGateway
let data: FiatGatewayOptions
beforeEach(() => {
gateway = FiatGateway.WERT
data = {} as FiatGatewayOptions
})
it('should return an action signaling the rquest of the fiat gawteway widget opening', () => {
expect(openFiatGatewayWidgetRequest(gateway, data)).toEqual({
meta: undefined,
payload: {
gateway,
data
},
type: OPEN_FIAT_GATEWAY_WIDGET_REQUEST
})
})
})

describe('when creating the action that signals the success of the fiat gateway widget request', () => {
it('should return an action signaling the rquest of the fiat gawteway widget opening', () => {
expect(openFiatGatewayWidgetSuccess()).toEqual({
meta: undefined,
type: OPEN_FIAT_GATEWAY_WIDGET_SUCCESS
})
})
})

describe('when creating the action that signals the failure of the fiat gateway widget request', () => {
let error: string
beforeEach(() => {
error = 'error'
})
it('should return an action signaling the rquest of the fiat gawteway widget opening', () => {
expect(openFiatGatewayWidgetFailure(error)).toEqual({
meta: undefined,
payload: {
error
},
type: OPEN_FIAT_GATEWAY_WIDGET_FAILURE
})
})
})
42 changes: 41 additions & 1 deletion src/modules/gateway/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { NetworkGatewayType } from 'decentraland-ui/dist/components/BuyManaWithF
import { getChainIdByNetwork } from '../../lib/eth'
import { buildTransactionWithFromPayload } from '../transaction/utils'
import { MoonPayTransactionStatus } from './moonpay/types'
import { Purchase } from './types'
import {
FiatGateway,
FiatGatewayListeners,
FiatGatewayOptions,
Purchase
} from './types'

// Open MANA-FIAT Gateway
export const OPEN_BUY_MANA_WITH_FIAT_MODAL_REQUEST =
Expand Down Expand Up @@ -155,3 +160,38 @@ export type PollPurchaseStatusSuccessAction = ReturnType<
export type PollPurchaseStatusFailureAction = ReturnType<
typeof pollPurchaseStatusFailure
>

// Open FIAT Gateway
export const OPEN_FIAT_GATEWAY_WIDGET_REQUEST =
'[Request] Open FIAT Gateway Widget'
export const OPEN_FIAT_GATEWAY_WIDGET_SUCCESS =
'[Success] Open FIAT Gateway Widget'
export const OPEN_FIAT_GATEWAY_WIDGET_FAILURE =
'[Failure] Open FIAT Gateway Widget'

export const openFiatGatewayWidgetRequest = (
gateway: FiatGateway,
data: FiatGatewayOptions,
listeners?: FiatGatewayListeners
) =>
action(OPEN_FIAT_GATEWAY_WIDGET_REQUEST, {
gateway,
data,
listeners
})

export const openFiatGatewayWidgetSuccess = () =>
action(OPEN_FIAT_GATEWAY_WIDGET_SUCCESS)

export const openFiatGatewayWidgetFailure = (error: string) =>
action(OPEN_FIAT_GATEWAY_WIDGET_FAILURE, { error })

export type OpenFiatGatewayWidgetRequestAction = ReturnType<
typeof openFiatGatewayWidgetRequest
>
export type OpenFiatGatewayWidgetSuccessAction = ReturnType<
typeof openFiatGatewayWidgetSuccess
>
export type OpenFiatGatewayWidgetFailureAction = ReturnType<
typeof openFiatGatewayWidgetFailure
>
145 changes: 141 additions & 4 deletions src/modules/gateway/sagas.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AuthIdentity } from 'decentraland-crypto-fetch'
import { load } from 'redux-persistence'
import { call, select } from 'redux-saga/effects'
import { expectSaga } from 'redux-saga-test-plan'
Expand All @@ -6,15 +7,21 @@ import { ChainId } from '@dcl/schemas/dist/dapps/chain-id'
import { Network } from '@dcl/schemas/dist/dapps/network'
import { NetworkGatewayType } from 'decentraland-ui/dist/components/BuyManaWithFiatModal/Network'
import { getChainIdByNetwork } from '../../lib/eth'
import { getAddress } from '../wallet/selectors'
import { getAddress, getData } from '../wallet/selectors'
import {
openFiatGatewayWidgetFailure,
openFiatGatewayWidgetRequest,
openFiatGatewayWidgetSuccess,
pollPurchaseStatusFailure,
pollPurchaseStatusRequest,
pollPurchaseStatusSuccess,
setPurchase
} from '../gateway/actions'
import { openModal } from '../modal/actions'
import { fetchWalletRequest } from '../wallet/actions'
import { MarketplaceAPI } from '../../lib/marketplaceApi'
import { Wallet } from '../wallet/types'
import { getIdentityOrRedirect } from '../identity/sagas'
import {
addManaPurchaseAsTransaction,
manaFiatGatewayPurchaseCompleted,
Expand All @@ -28,19 +35,31 @@ import {
} from './actions'
import { MoonPay } from './moonpay'
import { MoonPayTransaction, MoonPayTransactionStatus } from './moonpay/types'
import { createGatewaySaga } from './sagas'
import { NO_IDENTITY_ERROR, createGatewaySaga } from './sagas'
import { Transak } from './transak'
import { ManaFiatGatewaySagasConfig, Purchase, PurchaseStatus } from './types'
import {
FiatGateway,
GatewaySagasConfig,
Purchase,
PurchaseStatus,
WertOptions
} from './types'
import { getPendingManaPurchase, getPendingPurchases } from './selectors'
import { OrderResponse, TransakOrderStatus } from './transak/types'

jest.mock('@wert-io/widget-initializer')
jest.mock('../../lib/marketplaceApi')
jest.mock('../../lib/eth')

const mockGetChainIdByNetwork = getChainIdByNetwork as jest.MockedFunction<
typeof getChainIdByNetwork
>

const mockConfig: ManaFiatGatewaySagasConfig = {
const mockConfig: GatewaySagasConfig = {
[FiatGateway.WERT]: {
url: 'http://wert-url.xyz',
marketplaceServerURL: 'http://marketplace-server-url.xyz'
},
[NetworkGatewayType.MOON_PAY]: {
apiKey: 'api-key',
apiBaseUrl: 'http://moonpay-base.url.xyz',
Expand Down Expand Up @@ -842,3 +861,121 @@ describe('when handling the action signaling the load of the local storage into
})
})
})

describe('when handling the action signaling the opening of the fiat gateway widget', () => {
let wallet: Wallet
let wertOptions: WertOptions
let identity: AuthIdentity
let signedMessage: string
describe('when it is the WERT gateway', () => {
beforeEach(() => {
signedMessage = 'signedMessage'
wallet = {} as Wallet
})

describe('and there is identity', () => {
beforeEach(() => {
identity = {} as AuthIdentity
})
describe('and the marketplace server api call succeeds', () => {
describe('and has all the required data to sign', () => {
beforeEach(() => {
wertOptions = {
commodity: 'MANA',
commodity_amount: 100,
sc_address: '0x0',
sc_input_data: '0x0'
} as WertOptions
})
it('should put the success action', () => {
return expectSaga(gatewaySaga)
.put(openFiatGatewayWidgetSuccess())
.provide([
[select(getData), [wallet]],
[call(getIdentityOrRedirect), identity],
[
matchers.call.fn(MarketplaceAPI.prototype.signWertMessage),
signedMessage
]
])
.dispatch(
openFiatGatewayWidgetRequest(FiatGateway.WERT, wertOptions)
)
.silentRun()
})
})
describe('and does not have all the required data to sign', () => {
beforeEach(() => {
wertOptions = {} as WertOptions
})
it('should put the failure action', () => {
return expectSaga(gatewaySaga)
.put(
openFiatGatewayWidgetFailure(
'Missing data needed for the message to sign'
)
)
.provide([
[select(getData), [wallet]],
[call(getIdentityOrRedirect), identity]
])
.dispatch(
openFiatGatewayWidgetRequest(FiatGateway.WERT, wertOptions)
)
.silentRun()
})
})
})
describe('and the marketplace server api call fails', () => {
let error: string
beforeEach(() => {
error = 'error'
wertOptions = {
commodity: 'MANA',
commodity_amount: 100,
sc_address: '0x0',
sc_input_data: '0x0'
} as WertOptions
;(MarketplaceAPI.prototype
.signWertMessage as jest.Mock).mockRejectedValue({ message: error })
})
it('should put the failure action', () => {
return expectSaga(gatewaySaga)
.put(openFiatGatewayWidgetFailure(error))
.provide([
[select(getData), [wallet]],
[call(getIdentityOrRedirect), identity]
])
.dispatch(
openFiatGatewayWidgetRequest(FiatGateway.WERT, wertOptions)
)
.silentRun()
})
})
})

describe('and there is no identity', () => {
let error: string
beforeEach(() => {
wertOptions = {
commodity: 'MANA',
commodity_amount: 100,
sc_address: '0x0',
sc_input_data: '0x0'
} as WertOptions
;(MarketplaceAPI.prototype
.signWertMessage as jest.Mock).mockRejectedValue(error)
})
it('should put the failure action', () => {
return expectSaga(gatewaySaga)
.put(openFiatGatewayWidgetFailure(NO_IDENTITY_ERROR))
.provide([
[select(getData), [wallet]],
[call(getIdentityOrRedirect), undefined]
])
.dispatch(openFiatGatewayWidgetRequest(FiatGateway.WERT, wertOptions))
.silentRun()
})
})
})
})
Loading

0 comments on commit c08e7ff

Please sign in to comment.