@portal-hq/web 3.5.2 → 3.6.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -737,6 +737,21 @@ export const mockSourcesRes = {
737
737
  Uniswap_V3: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
738
738
  }
739
739
 
740
+ export const mockZeroXQuoteResponse = {
741
+ allowanceTarget: '0x1234567890123456789012345678901234567890',
742
+ cost: '1000000000000000',
743
+ transaction: {
744
+ from: '0x67aD8DCb5a4e8d6711C87D553C9325c3e85A1Da6',
745
+ to: '0x1234567890123456789012345678901234567890',
746
+ data: '0xabcdef',
747
+ gasLimit: '21000',
748
+ maxFeePerGas: '2000000000',
749
+ maxPriorityFeePerGas: '1000000000',
750
+ nonce: '1',
751
+ value: '1000000000000000000',
752
+ },
753
+ }
754
+
740
755
  export const mockOrganizationShare = 'test-org-share'
741
756
  export const mockDecryptedData = JSON.stringify({
742
757
  [PortalCurve.SECP256K1]: {
@@ -1096,3 +1111,204 @@ export const mockYieldXyzGetTransactionResponse = {
1096
1111
  transactionId: 'test-tx-id',
1097
1112
  },
1098
1113
  }
1114
+
1115
+ // LiFi mock data
1116
+ export const mockLifiToken = {
1117
+ address: '0x0000000000000000000000000000000000000000',
1118
+ symbol: 'ETH',
1119
+ decimals: 18,
1120
+ chainId: 'eip155:8453',
1121
+ name: 'Ethereum',
1122
+ logoURI: 'https://example.com/eth.png',
1123
+ priceUSD: '3000',
1124
+ }
1125
+
1126
+ export const mockLifiToToken = {
1127
+ address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
1128
+ symbol: 'USDC',
1129
+ decimals: 6,
1130
+ chainId: 'eip155:42161',
1131
+ name: 'USD Coin',
1132
+ logoURI: 'https://example.com/usdc.png',
1133
+ priceUSD: '1',
1134
+ }
1135
+
1136
+ export const mockLifiAction = {
1137
+ fromChainId: 'eip155:8453',
1138
+ fromAmount: '1000000000000',
1139
+ fromToken: mockLifiToken,
1140
+ toChainId: 'eip155:42161',
1141
+ toToken: mockLifiToToken,
1142
+ slippage: 0.005,
1143
+ fromAddress: mockEip155Address,
1144
+ toAddress: mockEip155Address,
1145
+ }
1146
+
1147
+ export const mockLifiEstimate = {
1148
+ tool: 'relay',
1149
+ fromAmount: '1000000000000',
1150
+ toAmount: '2990000000',
1151
+ toAmountMin: '2975050000',
1152
+ approvalAddress: '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE',
1153
+ executionDuration: 120,
1154
+ fromAmountUSD: '3000',
1155
+ toAmountUSD: '2990',
1156
+ feeCosts: [],
1157
+ gasCosts: [
1158
+ {
1159
+ type: 'SEND' as const,
1160
+ amount: '21000000000000',
1161
+ token: mockLifiToken,
1162
+ estimate: '21000',
1163
+ limit: '25000',
1164
+ amountUSD: '0.063',
1165
+ },
1166
+ ],
1167
+ }
1168
+
1169
+ export const mockLifiStep = {
1170
+ id: 'test-step-id',
1171
+ type: 'cross' as const,
1172
+ tool: 'relay',
1173
+ action: mockLifiAction,
1174
+ toolDetails: {
1175
+ key: 'relay',
1176
+ name: 'Relay',
1177
+ logoURI: 'https://example.com/relay.png',
1178
+ },
1179
+ estimate: mockLifiEstimate,
1180
+ integrator: 'portal',
1181
+ transactionRequest: {
1182
+ to: '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE',
1183
+ from: mockEip155Address,
1184
+ data: '0x123456',
1185
+ value: '0x0de0b6b3a7640000',
1186
+ gasLimit: '0x61a8',
1187
+ chainId: 'eip155:1',
1188
+ },
1189
+ }
1190
+
1191
+ export const mockLifiRoute = {
1192
+ id: 'test-route-id',
1193
+ fromChainId: 'eip155:8453',
1194
+ fromAmountUSD: '3000',
1195
+ fromAmount: '1000000000000',
1196
+ fromToken: mockLifiToken,
1197
+ toChainId: 'eip155:42161',
1198
+ toAmountUSD: '2990',
1199
+ toAmount: '2990000000',
1200
+ toAmountMin: '2975050000',
1201
+ toToken: mockLifiToToken,
1202
+ steps: [mockLifiStep],
1203
+ gasCostUSD: '0.063',
1204
+ fromAddress: mockEip155Address,
1205
+ toAddress: mockEip155Address,
1206
+ tags: ['RECOMMENDED', 'FASTEST'],
1207
+ }
1208
+
1209
+ export const mockLifiGetRoutesRequest = {
1210
+ fromChainId: 'eip155:8453',
1211
+ fromAmount: '1000000000000',
1212
+ fromTokenAddress: 'ETH',
1213
+ toChainId: 'eip155:42161',
1214
+ toTokenAddress: 'USDC',
1215
+ fromAddress: mockEip155Address,
1216
+ toAddress: mockEip155Address,
1217
+ options: {
1218
+ slippage: 0.005,
1219
+ integrator: 'portal',
1220
+ },
1221
+ }
1222
+
1223
+ export const mockLifiGetRoutesResponse = {
1224
+ data: {
1225
+ rawResponse: {
1226
+ routes: [mockLifiRoute],
1227
+ unavailableRoutes: {
1228
+ filteredOut: [],
1229
+ failed: [],
1230
+ },
1231
+ },
1232
+ },
1233
+ }
1234
+
1235
+ export const mockLifiGetQuoteRequest = {
1236
+ fromChain: 'eip155:8453',
1237
+ toChain: 'eip155:42161',
1238
+ fromToken: 'ETH',
1239
+ toToken: 'USDC',
1240
+ fromAddress: mockEip155Address,
1241
+ fromAmount: '1000000000000',
1242
+ toAddress: mockEip155Address,
1243
+ order: 'FASTEST' as const,
1244
+ slippage: 0.005,
1245
+ integrator: 'portal',
1246
+ }
1247
+
1248
+ export const mockLifiGetQuoteResponse = {
1249
+ data: {
1250
+ rawResponse: mockLifiStep,
1251
+ },
1252
+ }
1253
+
1254
+ export const mockLifiGetStatusRequest = {
1255
+ txHash: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
1256
+ bridge: 'relay' as const,
1257
+ fromChain: '1',
1258
+ toChain: '137',
1259
+ }
1260
+
1261
+ export const mockLifiGetStatusResponse = {
1262
+ data: {
1263
+ rawResponse: {
1264
+ sending: {
1265
+ txHash:
1266
+ '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
1267
+ txLink:
1268
+ 'https://etherscan.io/tx/0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
1269
+ amount: '1000000000000000000',
1270
+ token: mockLifiToken,
1271
+ chainId: '1',
1272
+ gasAmount: '21000000000000',
1273
+ gasAmountUSD: '0.063',
1274
+ timestamp: 1704067200,
1275
+ },
1276
+ receiving: {
1277
+ chainId: '137',
1278
+ txHash:
1279
+ '0x0987654321fedcba0987654321fedcba0987654321fedcba0987654321fedcba',
1280
+ txLink:
1281
+ 'https://polygonscan.com/tx/0x0987654321fedcba0987654321fedcba0987654321fedcba0987654321fedcba',
1282
+ token: mockLifiToToken,
1283
+ amount: '2990000000',
1284
+ timestamp: 1704067320,
1285
+ },
1286
+ status: 'DONE' as const,
1287
+ substatus: 'COMPLETED' as const,
1288
+ substatusMessage: 'Transfer completed successfully',
1289
+ tool: 'relay',
1290
+ transactionId: 'test-lifi-tx-id',
1291
+ fromAddress: mockEip155Address,
1292
+ toAddress: mockEip155Address,
1293
+ lifiExplorerLink: 'https://explorer.li.fi/tx/test-lifi-tx-id',
1294
+ },
1295
+ },
1296
+ }
1297
+
1298
+ export const mockLifiGetRouteStepRequest = mockLifiStep
1299
+
1300
+ export const mockLifiGetRouteStepResponse = {
1301
+ data: {
1302
+ rawResponse: {
1303
+ ...mockLifiStep,
1304
+ transactionRequest: {
1305
+ to: '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE',
1306
+ from: mockEip155Address,
1307
+ data: '0x123456789abcdef',
1308
+ value: '0x0de0b6b3a7640000',
1309
+ gasLimit: '0x61a8',
1310
+ chainId: 'eip155:1',
1311
+ },
1312
+ },
1313
+ },
1314
+ }
package/src/index.ts CHANGED
@@ -44,6 +44,7 @@ import {
44
44
  import Mpc from './mpc'
45
45
  import Provider, { RequestMethod } from './provider'
46
46
  import Yield from './integrations/yield'
47
+ import Trading from './integrations/trading'
47
48
 
48
49
  class Portal {
49
50
  public address?: string
@@ -56,6 +57,7 @@ class Portal {
56
57
  public host: string
57
58
  public mpc: Mpc
58
59
  public yield: Yield
60
+ public trading: Trading
59
61
  public mpcHost: string
60
62
  public mpcVersion: string
61
63
  public provider: Provider
@@ -109,6 +111,8 @@ class Portal {
109
111
 
110
112
  this.yield = new Yield({ mpc: this.mpc })
111
113
 
114
+ this.trading = new Trading({ mpc: this.mpc })
115
+
112
116
  this.provider = new Provider({
113
117
  portal: this,
114
118
  chainId: chainId ? Number(chainId) : undefined,
@@ -887,6 +891,9 @@ class Portal {
887
891
  * Swaps Methods
888
892
  *******************************/
889
893
 
894
+ /**
895
+ * @deprecated This method is deprecated. Use `portal.trading.zeroX.getQuote` instead.
896
+ */
890
897
  public async getQuote(
891
898
  apiKey: string,
892
899
  args: QuoteArgs,
@@ -895,6 +902,9 @@ class Portal {
895
902
  return this.mpc?.getQuote(apiKey, args, chainId)
896
903
  }
897
904
 
905
+ /**
906
+ * @deprecated This method is deprecated. Use `portal.trading.zeroX.getSources` instead.
907
+ */
898
908
  public async getSources(
899
909
  apiKey: string,
900
910
  chainId: string,
@@ -1042,6 +1052,9 @@ export {
1042
1052
  } from '../yieldxyz-types'
1043
1053
 
1044
1054
  export { MpcError, MpcErrorCodes } from './mpc'
1055
+
1056
+ export { PortalMpcError } from './mpc/errors'
1057
+
1045
1058
  export {
1046
1059
  type Address,
1047
1060
  type Dapp,
@@ -0,0 +1,17 @@
1
+ import Mpc from '../../mpc'
2
+ import ZeroX from './zero-x'
3
+ import LiFi from './lifi'
4
+
5
+ /**
6
+ * This class is a container for the LiFi class.
7
+ * In the future, Trading domain logic should be here.
8
+ */
9
+ export default class Trading {
10
+ public lifi: LiFi
11
+ public zeroX: ZeroX
12
+
13
+ constructor({ mpc }: { mpc: Mpc }) {
14
+ this.lifi = new LiFi({ mpc })
15
+ this.zeroX = new ZeroX({ mpc })
16
+ }
17
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ import LiFi from '.'
6
+ import Mpc from '../../../mpc'
7
+ import portalMock from '../../../__mocks/portal/portal'
8
+ import {
9
+ mockHost,
10
+ mockLifiGetRoutesRequest,
11
+ mockLifiGetRoutesResponse,
12
+ mockLifiGetQuoteRequest,
13
+ mockLifiGetQuoteResponse,
14
+ mockLifiGetStatusRequest,
15
+ mockLifiGetStatusResponse,
16
+ mockLifiGetRouteStepRequest,
17
+ mockLifiGetRouteStepResponse,
18
+ } from '../../../__mocks/constants'
19
+
20
+ describe('LiFi', () => {
21
+ let lifi: LiFi
22
+ let mpc: Mpc
23
+
24
+ beforeEach(() => {
25
+ jest.clearAllMocks()
26
+
27
+ portalMock.host = mockHost
28
+ mpc = new Mpc({
29
+ portal: portalMock,
30
+ })
31
+
32
+ lifi = new LiFi({ mpc })
33
+ })
34
+
35
+ describe('getRoutes', () => {
36
+ it('should call mpc.getLifiRoutes with the correct arguments', async () => {
37
+ const spy = jest
38
+ .spyOn(mpc, 'getLifiRoutes')
39
+ .mockResolvedValue(mockLifiGetRoutesResponse)
40
+
41
+ const result = await lifi.getRoutes(mockLifiGetRoutesRequest)
42
+
43
+ expect(spy).toHaveBeenCalledWith(mockLifiGetRoutesRequest)
44
+ expect(result).toEqual(mockLifiGetRoutesResponse)
45
+ })
46
+
47
+ it('should propagate errors from mpc.getLifiRoutes', async () => {
48
+ const error = new Error('Test error')
49
+ jest.spyOn(mpc, 'getLifiRoutes').mockRejectedValue(error)
50
+
51
+ await expect(lifi.getRoutes(mockLifiGetRoutesRequest)).rejects.toThrow(
52
+ 'Test error',
53
+ )
54
+ })
55
+ })
56
+
57
+ describe('getQuote', () => {
58
+ it('should call mpc.getLifiQuote with the correct arguments', async () => {
59
+ const spy = jest
60
+ .spyOn(mpc, 'getLifiQuote')
61
+ .mockResolvedValue(mockLifiGetQuoteResponse)
62
+
63
+ const result = await lifi.getQuote(mockLifiGetQuoteRequest)
64
+
65
+ expect(spy).toHaveBeenCalledWith(mockLifiGetQuoteRequest)
66
+ expect(result).toEqual(mockLifiGetQuoteResponse)
67
+ })
68
+
69
+ it('should propagate errors from mpc.getLifiQuote', async () => {
70
+ const error = new Error('Test error')
71
+ jest.spyOn(mpc, 'getLifiQuote').mockRejectedValue(error)
72
+
73
+ await expect(lifi.getQuote(mockLifiGetQuoteRequest)).rejects.toThrow(
74
+ 'Test error',
75
+ )
76
+ })
77
+ })
78
+
79
+ describe('getStatus', () => {
80
+ it('should call mpc.getLifiStatus with the correct arguments', async () => {
81
+ const spy = jest
82
+ .spyOn(mpc, 'getLifiStatus')
83
+ .mockResolvedValue(mockLifiGetStatusResponse)
84
+
85
+ const result = await lifi.getStatus(mockLifiGetStatusRequest)
86
+
87
+ expect(spy).toHaveBeenCalledWith(mockLifiGetStatusRequest)
88
+ expect(result).toEqual(mockLifiGetStatusResponse)
89
+ })
90
+
91
+ it('should propagate errors from mpc.getLifiStatus', async () => {
92
+ const error = new Error('Test error')
93
+ jest.spyOn(mpc, 'getLifiStatus').mockRejectedValue(error)
94
+
95
+ await expect(lifi.getStatus(mockLifiGetStatusRequest)).rejects.toThrow(
96
+ 'Test error',
97
+ )
98
+ })
99
+ })
100
+
101
+ describe('getRouteStep', () => {
102
+ it('should call mpc.getLifiRouteStep with the correct arguments', async () => {
103
+ const spy = jest
104
+ .spyOn(mpc, 'getLifiRouteStep')
105
+ .mockResolvedValue(mockLifiGetRouteStepResponse)
106
+
107
+ const result = await lifi.getRouteStep(mockLifiGetRouteStepRequest)
108
+
109
+ expect(spy).toHaveBeenCalledWith(mockLifiGetRouteStepRequest)
110
+ expect(result).toEqual(mockLifiGetRouteStepResponse)
111
+ })
112
+
113
+ it('should propagate errors from mpc.getLifiRouteStep', async () => {
114
+ const error = new Error('Test error')
115
+ jest.spyOn(mpc, 'getLifiRouteStep').mockRejectedValue(error)
116
+
117
+ await expect(
118
+ lifi.getRouteStep(mockLifiGetRouteStepRequest),
119
+ ).rejects.toThrow('Test error')
120
+ })
121
+ })
122
+ })
@@ -0,0 +1,61 @@
1
+ import Mpc from '../../../mpc'
2
+ import {
3
+ LifiQuoteRequest,
4
+ LifiQuoteResponse,
5
+ LifiRoutesRequest,
6
+ LifiRoutesResponse,
7
+ LifiStatusRequest,
8
+ LifiStatusResponse,
9
+ LifiStepTransactionRequest,
10
+ LifiStepTransactionResponse,
11
+ } from '../../../../lifi-types'
12
+
13
+ export default class LiFi {
14
+ private mpc: Mpc
15
+
16
+ constructor({ mpc }: { mpc: Mpc }) {
17
+ this.mpc = mpc
18
+ }
19
+
20
+ /**
21
+ * Retrieves routes from the Li.Fi integration.
22
+ * @param data - The parameters for the routes request.
23
+ * @returns A `LifiRoutesResponse` promise.
24
+ * @throws An error if the operation fails.
25
+ */
26
+ public async getRoutes(data: LifiRoutesRequest): Promise<LifiRoutesResponse> {
27
+ return this.mpc?.getLifiRoutes(data)
28
+ }
29
+
30
+ /**
31
+ * Retrieves a quote from the Li.Fi integration.
32
+ * @param data - The parameters for the quote request.
33
+ * @returns A `LifiQuoteResponse` promise.
34
+ * @throws An error if the operation fails.
35
+ */
36
+ public async getQuote(data: LifiQuoteRequest): Promise<LifiQuoteResponse> {
37
+ return this.mpc?.getLifiQuote(data)
38
+ }
39
+
40
+ /**
41
+ * Retrieves the status of a transaction from the Li.Fi integration.
42
+ * @param data - The parameters for the status request.
43
+ * @returns A `LifiStatusResponse` promise.
44
+ * @throws An error if the operation fails.
45
+ */
46
+ public async getStatus(data: LifiStatusRequest): Promise<LifiStatusResponse> {
47
+ return this.mpc?.getLifiStatus(data)
48
+ }
49
+
50
+ /**
51
+ * Retrieves an unsigned transaction from the Li.Fi integration that has yet to be signed/submitted.
52
+ * @param data - The step transaction request containing the step details.
53
+ * @returns A `LifiStepTransactionResponse` promise.
54
+ * @throws An error if the operation fails.
55
+ */
56
+ public async getRouteStep(
57
+ data: LifiStepTransactionRequest,
58
+ ): Promise<LifiStepTransactionResponse> {
59
+ return this.mpc?.getLifiRouteStep(data)
60
+ }
61
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ import ZeroX from '.'
6
+ import Mpc from '../../../mpc'
7
+ import portalMock from '../../../__mocks/portal/portal'
8
+ import {
9
+ mockHost,
10
+ mockQuoteArgs,
11
+ mockSourcesRes,
12
+ mockZeroXQuoteResponse,
13
+ } from '../../../__mocks/constants'
14
+
15
+ describe('ZeroX', () => {
16
+ let zeroX: ZeroX
17
+ let mpc: Mpc
18
+
19
+ beforeEach(() => {
20
+ jest.clearAllMocks()
21
+
22
+ portalMock.host = mockHost
23
+ mpc = new Mpc({
24
+ portal: portalMock,
25
+ })
26
+
27
+ zeroX = new ZeroX({ mpc })
28
+ })
29
+
30
+ describe('getQuote', () => {
31
+ it('should correctly call mpc.getQuote', async () => {
32
+ const spy = jest
33
+ .spyOn(mpc, 'getQuote')
34
+ .mockResolvedValue(mockZeroXQuoteResponse)
35
+
36
+ const result = await zeroX.getQuote('test', mockQuoteArgs, 'eip155:1')
37
+
38
+ expect(spy).toHaveBeenCalledTimes(1)
39
+ expect(spy).toHaveBeenCalledWith('test', mockQuoteArgs, 'eip155:1')
40
+ expect(result).toEqual(mockZeroXQuoteResponse)
41
+ })
42
+
43
+ it('should propagate errors from mpc.getQuote', async () => {
44
+ const error = new Error('Test error')
45
+ jest.spyOn(mpc, 'getQuote').mockRejectedValue(error)
46
+
47
+ await expect(
48
+ zeroX.getQuote('test', mockQuoteArgs, 'eip155:1'),
49
+ ).rejects.toThrow('Test error')
50
+ })
51
+ })
52
+
53
+ describe('getSources', () => {
54
+ it('should correctly call mpc.getSources', async () => {
55
+ const spy = jest
56
+ .spyOn(mpc, 'getSources')
57
+ .mockResolvedValue(mockSourcesRes)
58
+
59
+ const result = await zeroX.getSources('test', 'eip155:1')
60
+
61
+ expect(spy).toHaveBeenCalledTimes(1)
62
+ expect(spy).toHaveBeenCalledWith('test', 'eip155:1')
63
+ expect(result).toEqual(mockSourcesRes)
64
+ })
65
+
66
+ it('should propagate errors from mpc.getSources', async () => {
67
+ const error = new Error('Test error')
68
+ jest.spyOn(mpc, 'getSources').mockRejectedValue(error)
69
+
70
+ await expect(zeroX.getSources('test', 'eip155:1')).rejects.toThrow(
71
+ 'Test error',
72
+ )
73
+ })
74
+ })
75
+ })
@@ -0,0 +1,40 @@
1
+ import Mpc from '../../../mpc'
2
+ import { QuoteArgs, QuoteResponse } from '../../../../types'
3
+
4
+ export default class ZeroX {
5
+ private mpc: Mpc
6
+
7
+ constructor({ mpc }: { mpc: Mpc }) {
8
+ this.mpc = mpc
9
+ }
10
+
11
+ /**
12
+ * Get a quote from the Swaps API.
13
+ *
14
+ * @param apiKey - The API key for the Swaps API.
15
+ * @param args - The arguments for the quote.
16
+ * @param chainId - The chain ID for the quote.
17
+ * @returns The quote response.
18
+ */
19
+ public async getQuote(
20
+ apiKey: string,
21
+ args: QuoteArgs,
22
+ chainId: string,
23
+ ): Promise<QuoteResponse> {
24
+ return this.mpc?.getQuote(apiKey, args, chainId)
25
+ }
26
+
27
+ /**
28
+ * Get the valid, swappable token sources that can be used with your Portal MPC Wallet.
29
+ *
30
+ * @param apiKey - The API key for the Swaps API.
31
+ * @param chainId - The chain ID for the sources.
32
+ * @returns The sources response.
33
+ */
34
+ public async getSources(
35
+ apiKey: string,
36
+ chainId: string,
37
+ ): Promise<Record<string, string>> {
38
+ return this.mpc?.getSources(apiKey, chainId)
39
+ }
40
+ }