@imtbl/sdk 0.1.10-alpha.4 → 0.2.1-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.
package/dist/orderbook.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import axios from 'axios';
2
2
  import FormData from 'form-data';
3
3
  import { providers, BigNumber, constants, utils, Contract } from 'ethers';
4
- import dotenv from 'dotenv';
5
4
  import { Seaport as Seaport$1 } from '@opensea/seaport-js';
6
5
 
7
6
  class BaseHttpRequest {
@@ -899,49 +898,11 @@ class ImmutableApiClientFactory {
899
898
  }
900
899
  }
901
900
 
902
- // eslint-disable-next-line
903
- dotenv.config();
904
- function getConfigFromEnv() {
905
- if (!process.env.ORDERBOOK_MR_API_URL
906
- || !process.env.SEAPORT_CONTRACT_ADDRESS
907
- || !process.env.ZONE_CONTRACT_ADDRESS) {
908
- throw new Error('missing config');
909
- }
910
- return {
911
- apiUrl: process.env.ORDERBOOK_MR_API_URL,
912
- seaportContractAddress: process.env.SEAPORT_CONTRACT_ADDRESS,
913
- zoneContractAddress: process.env.ZONE_CONTRACT_ADDRESS,
914
- };
915
- }
916
-
917
- dotenv.config();
918
- function getLocalhostProvider() {
919
- const endpoint = process.env.LOCAL_RPC_ENDPOINT;
920
- if (!endpoint) {
921
- throw new Error('LOCAL_RPC_ENDPOINT not set');
922
- }
923
- return new providers.JsonRpcProvider(endpoint);
924
- }
925
-
926
- dotenv.config();
927
-
928
- const LOCAL_CHAIN_NAME = 'imtbl-zkevm-local';
929
901
  const DEVNET_CHAIN_NAME = 'imtbl-zkevm-devnet';
930
902
  const TESTNET_CHAIN_NAME = 'imtbl-zkevm-testnet';
931
903
  const MAINNET_CHAIN_NAME = 'imtbl-zkevm-mainnet';
932
- function localChainConfig() {
933
- const localConfig = getConfigFromEnv();
934
- return {
935
- seaportContractAddress: localConfig.seaportContractAddress,
936
- zoneContractAddress: localConfig.zoneContractAddress,
937
- apiEndpoint: localConfig.apiUrl,
938
- provider: getLocalhostProvider(),
939
- };
940
- }
941
904
  function getOrderbookConfig(chainName) {
942
905
  switch (chainName) {
943
- case LOCAL_CHAIN_NAME:
944
- return localChainConfig();
945
906
  case DEVNET_CHAIN_NAME:
946
907
  return {
947
908
  seaportContractAddress: '0x41388404Efb7a68Fd31d75CEf71dF91e2BDBa2fb',
@@ -1917,24 +1878,26 @@ class SeaportLibFactory {
1917
1878
  * @param {OrderbookModuleConfiguration} config - Configuration for Immutable services.
1918
1879
  */
1919
1880
  class Orderbook {
1920
- config;
1921
1881
  apiClient;
1922
1882
  seaport;
1923
- constructor(config) {
1924
- this.config = config;
1925
- const orderbookConfig = getOrderbookConfig(config.overrides?.chainName);
1926
- const apiEndpoint = orderbookConfig?.apiEndpoint;
1883
+ orderbookConfig;
1884
+ constructor(config, localConfig) {
1885
+ // either use preconfigured config or local config passed in
1886
+ const maybeConfig = getOrderbookConfig(config.overrides?.chainName) || localConfig;
1887
+ if (!maybeConfig) {
1888
+ throw new Error('Orderbook configuration not passed, either select one of the preseet chainNames or pass in localConfig');
1889
+ }
1890
+ const { chainName, provider } = config.overrides;
1891
+ maybeConfig.provider = provider
1892
+ || maybeConfig.provider;
1893
+ this.orderbookConfig = maybeConfig;
1894
+ const { apiEndpoint } = this.orderbookConfig;
1927
1895
  if (!apiEndpoint) {
1928
1896
  throw new Error('API endpoint must be provided as an override');
1929
1897
  }
1930
- // TODO: Move chainId lookup to a map based on env. Just using override to get dev started
1931
- const chainName = config.overrides?.chainName;
1932
- if (!chainName) {
1933
- throw new Error('chainName must be provided as an override');
1934
- }
1935
- this.apiClient = new ImmutableApiClientFactory(apiEndpoint, chainName, orderbookConfig.seaportContractAddress).create();
1936
- const seaportLibFactory = new SeaportLibFactory(orderbookConfig.seaportContractAddress, orderbookConfig.provider);
1937
- this.seaport = new Seaport(seaportLibFactory, orderbookConfig.provider, orderbookConfig.seaportContractAddress, orderbookConfig.zoneContractAddress);
1898
+ this.apiClient = new ImmutableApiClientFactory(apiEndpoint, chainName, this.orderbookConfig.seaportContractAddress).create();
1899
+ const seaportLibFactory = new SeaportLibFactory(this.orderbookConfig.seaportContractAddress, this.orderbookConfig.provider);
1900
+ this.seaport = new Seaport(seaportLibFactory, this.orderbookConfig.provider, this.orderbookConfig.seaportContractAddress, this.orderbookConfig.zoneContractAddress);
1938
1901
  }
1939
1902
  /**
1940
1903
  * Get an order by ID
@@ -1962,8 +1925,7 @@ class Orderbook {
1962
1925
  * the order components that can be submitted to `createListing` with a signature.
1963
1926
  */
1964
1927
  async prepareListing({ makerAddress, sell, buy, orderExpiry, }) {
1965
- const orderbookConfig = getOrderbookConfig(this.config.overrides?.chainName);
1966
- const erc721 = new ERC721Factory(sell.contractAddress, orderbookConfig.provider).create();
1928
+ const erc721 = new ERC721Factory(sell.contractAddress, this.orderbookConfig.provider).create();
1967
1929
  const royaltyInfo = await erc721.royaltyInfo(sell.tokenId, buy.amount);
1968
1930
  return this.seaport.prepareSeaportOrder(makerAddress, sell, buy, royaltyInfo,
1969
1931
  // Default order start to now
@@ -10488,6 +10488,19 @@ interface PassportOverrides {
10488
10488
  }
10489
10489
  interface PassportModuleConfiguration extends ModuleConfiguration<PassportOverrides>, OidcConfiguration {
10490
10490
  }
10491
+ type DeviceConnectResponse = {
10492
+ code: string;
10493
+ deviceCode: string;
10494
+ url: string;
10495
+ interval: number;
10496
+ };
10497
+ type DeviceTokenResponse = {
10498
+ access_token: string;
10499
+ refresh_token?: string;
10500
+ id_token: string;
10501
+ token_type: string;
10502
+ expires_in: number;
10503
+ };
10491
10504
 
10492
10505
  declare class Passport {
10493
10506
  private readonly authManager;
@@ -10499,11 +10512,20 @@ declare class Passport {
10499
10512
  constructor(passportModuleConfiguration: PassportModuleConfiguration);
10500
10513
  connectImxSilent(): Promise<IMXProvider | null>;
10501
10514
  connectImx(): Promise<IMXProvider>;
10515
+ loginWithDeviceFlow(): Promise<DeviceConnectResponse>;
10516
+ connectImxDeviceFlow(deviceCode: string, interval: number, timeoutMs?: number): Promise<IMXProvider | null>;
10517
+ /**
10518
+ * @returns {boolean} the stored device flow credentials if they exist
10519
+ */
10520
+ checkStoredDeviceFlowCredentials(): DeviceTokenResponse | null;
10521
+ connectImxWithCredentials(tokenResponse: DeviceTokenResponse): Promise<IMXProvider | null>;
10502
10522
  loginCallback(): Promise<void>;
10503
10523
  logout(): Promise<void>;
10524
+ logoutDeviceFlow(): Promise<void>;
10504
10525
  getUserInfo(): Promise<UserProfile | undefined>;
10526
+ getUserInfoDeviceFlow(): Promise<UserProfile | undefined>;
10505
10527
  getIdToken(): Promise<string | undefined>;
10506
10528
  getAccessToken(): Promise<string | undefined>;
10507
10529
  }
10508
10530
 
10509
- export { Networks, Passport, PassportError, PassportModuleConfiguration, PassportOverrides, UserProfile };
10531
+ export { DeviceConnectResponse, DeviceTokenResponse, Networks, Passport, PassportError, PassportModuleConfiguration, PassportOverrides, UserProfile };
package/dist/passport.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { ImmutableX, Config, generateLegacyStarkPrivateKey, createStarkSigner } from '@imtbl/core-sdk';
2
2
  import { UserManager } from 'oidc-client-ts';
3
+ import axios$1 from 'axios';
4
+ import jwt_decode from 'jwt-decode';
3
5
  import { Magic } from 'magic-sdk';
4
6
  import { OpenIdExtension } from '@magic-ext/oidc';
5
7
  import BN from 'bn.js';
@@ -21,7 +23,7 @@ const createImmutableXConfiguration = ({ basePath, chainID, coreContractAddress,
21
23
  chainID,
22
24
  coreContractAddress,
23
25
  registrationContractAddress,
24
- sdkVersion: 'ts-immutable-sdk-0.1.10-alpha.4',
26
+ sdkVersion: 'ts-immutable-sdk-0.2.1-alpha',
25
27
  });
26
28
  /**
27
29
  * Sets `sdkVersion` at the time of build
@@ -4289,6 +4291,43 @@ const withPassportError = async (fn, customErrorType) => {
4289
4291
  }
4290
4292
  };
4291
4293
 
4294
+ const keyCrendentials = 'passport_credentials';
4295
+ const validCredentialsMinTtlSec = 3600; // 1 hour
4296
+ class DeviceCredentialsManager {
4297
+ saveCredentials(tokenResponse) {
4298
+ localStorage.setItem(keyCrendentials, JSON.stringify(tokenResponse));
4299
+ }
4300
+ getCredentials() {
4301
+ const credentialsJson = localStorage.getItem(keyCrendentials);
4302
+ if (credentialsJson) {
4303
+ return JSON.parse(credentialsJson);
4304
+ }
4305
+ return null;
4306
+ }
4307
+ areValid(tokenResponse) {
4308
+ if (tokenResponse) {
4309
+ const accessTokenValid = this.isTokenValid(tokenResponse.access_token);
4310
+ const idTokenValid = this.isTokenValid(tokenResponse.id_token);
4311
+ return accessTokenValid && idTokenValid;
4312
+ }
4313
+ return false;
4314
+ }
4315
+ isTokenValid(jwt) {
4316
+ const tokenPayload = jwt_decode(jwt);
4317
+ const expiresAt = tokenPayload.exp ?? 0;
4318
+ const now = (Date.now() / 1000) + validCredentialsMinTtlSec;
4319
+ return expiresAt > now;
4320
+ }
4321
+ clearCredentials() {
4322
+ localStorage.removeItem(keyCrendentials);
4323
+ }
4324
+ }
4325
+
4326
+ const formUrlEncodedHeader = {
4327
+ headers: {
4328
+ 'Content-Type': 'application/x-www-form-urlencoded',
4329
+ },
4330
+ };
4292
4331
  const getAuthConfiguration = ({ oidcConfiguration, authenticationDomain, }) => {
4293
4332
  const baseConfiguration = {
4294
4333
  authority: authenticationDomain,
@@ -4314,12 +4353,19 @@ const getAuthConfiguration = ({ oidcConfiguration, authenticationDomain, }) => {
4314
4353
  }
4315
4354
  return baseConfiguration;
4316
4355
  };
4356
+ function wait$1(ms) {
4357
+ return new Promise((resolve) => {
4358
+ setTimeout(resolve, ms);
4359
+ });
4360
+ }
4317
4361
  class AuthManager {
4318
4362
  userManager;
4319
4363
  config;
4364
+ deviceCredentialsManager;
4320
4365
  constructor(config) {
4321
4366
  this.config = config;
4322
4367
  this.userManager = new UserManager(getAuthConfiguration(config));
4368
+ this.deviceCredentialsManager = new DeviceCredentialsManager();
4323
4369
  }
4324
4370
  static mapOidcUserToDomainModel = (oidcUser) => {
4325
4371
  const passport = oidcUser.profile?.passport;
@@ -4349,6 +4395,27 @@ class AuthManager {
4349
4395
  }
4350
4396
  return user;
4351
4397
  };
4398
+ static mapDeviceTokenResponseToDomainUserModel = (tokenResponse) => {
4399
+ const idTokenPayload = jwt_decode(tokenResponse.id_token);
4400
+ const user = {
4401
+ idToken: tokenResponse.id_token,
4402
+ accessToken: tokenResponse.access_token,
4403
+ refreshToken: tokenResponse.refresh_token,
4404
+ profile: {
4405
+ sub: idTokenPayload.sub,
4406
+ email: idTokenPayload.email,
4407
+ nickname: idTokenPayload.nickname,
4408
+ },
4409
+ };
4410
+ if (idTokenPayload?.passport?.imx_eth_address) {
4411
+ user.imx = {
4412
+ ethAddress: idTokenPayload?.passport?.imx_eth_address,
4413
+ starkAddress: idTokenPayload?.passport?.imx_stark_address,
4414
+ userAdminAddress: idTokenPayload?.passport?.imx_user_admin_address,
4415
+ };
4416
+ }
4417
+ return user;
4418
+ };
4352
4419
  async login() {
4353
4420
  return withPassportError(async () => {
4354
4421
  const popupWindowFeatures = { width: 410, height: 450 };
@@ -4361,9 +4428,104 @@ class AuthManager {
4361
4428
  async loginCallback() {
4362
4429
  return withPassportError(async () => this.userManager.signinPopupCallback(), PassportErrorType.AUTHENTICATION_ERROR);
4363
4430
  }
4431
+ async loginWithDeviceFlow() {
4432
+ return withPassportError(async () => {
4433
+ const response = await axios$1.post(`${this.config.authenticationDomain}/oauth/device/code`, {
4434
+ client_id: this.config.oidcConfiguration.clientId,
4435
+ scope: this.config.oidcConfiguration.scope,
4436
+ audience: this.config.oidcConfiguration.audience,
4437
+ }, formUrlEncodedHeader);
4438
+ return {
4439
+ code: response.data.user_code,
4440
+ deviceCode: response.data.device_code,
4441
+ url: response.data.verification_uri_complete,
4442
+ interval: response.data.interval,
4443
+ };
4444
+ }, PassportErrorType.AUTHENTICATION_ERROR);
4445
+ }
4446
+ /* eslint-disable no-await-in-loop */
4447
+ async connectImxDeviceFlow(deviceCode, interval, timeoutMs) {
4448
+ return withPassportError(async () => {
4449
+ const startTime = Date.now();
4450
+ const loopCondition = true;
4451
+ while (loopCondition) {
4452
+ if (timeoutMs != null && Date.now() - startTime > timeoutMs) {
4453
+ throw new Error('Timed out');
4454
+ }
4455
+ await wait$1(interval * 1000);
4456
+ try {
4457
+ const tokenResponse = await this.getDeviceFlowToken(deviceCode);
4458
+ const user = AuthManager.mapDeviceTokenResponseToDomainUserModel(tokenResponse);
4459
+ // Only persist credentials that contain the necessary data
4460
+ if (user.imx?.ethAddress && user.imx?.starkAddress && user.imx?.userAdminAddress) {
4461
+ this.deviceCredentialsManager.saveCredentials(tokenResponse);
4462
+ }
4463
+ return user;
4464
+ }
4465
+ catch (error) {
4466
+ if (axios$1.isAxiosError(error)) {
4467
+ const responseError = error.response?.data;
4468
+ switch (responseError.error) {
4469
+ case 'authorization_pending':
4470
+ break;
4471
+ case 'slow_down':
4472
+ break;
4473
+ case 'expired_token':
4474
+ throw new Error('Token expired, please log in again');
4475
+ case 'access_denied':
4476
+ throw new Error('User denied access');
4477
+ default:
4478
+ throw new Error('Error getting token');
4479
+ }
4480
+ }
4481
+ else {
4482
+ throw error;
4483
+ }
4484
+ }
4485
+ }
4486
+ throw new Error('Failed to get credentials');
4487
+ }, PassportErrorType.AUTHENTICATION_ERROR);
4488
+ }
4489
+ /* eslint-enable no-await-in-loop */
4490
+ async getDeviceFlowToken(deviceCode) {
4491
+ const response = await axios$1.post(`${this.config.authenticationDomain}/oauth/token`, {
4492
+ client_id: this.config.oidcConfiguration.clientId,
4493
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
4494
+ device_code: deviceCode,
4495
+ }, formUrlEncodedHeader);
4496
+ return response.data;
4497
+ }
4498
+ async connectImxWithCredentials(tokenResponse) {
4499
+ return withPassportError(async () => {
4500
+ if (this.deviceCredentialsManager.areValid(tokenResponse)) {
4501
+ // Credentials exist and are still valid
4502
+ return AuthManager.mapDeviceTokenResponseToDomainUserModel(tokenResponse);
4503
+ }
4504
+ const refreshToken = tokenResponse?.refresh_token ?? null;
4505
+ if (refreshToken) {
4506
+ // Token is no longer valid, but refresh token can be used to a new one
4507
+ const newTokenResponse = await this.refreshToken(refreshToken);
4508
+ return AuthManager.mapDeviceTokenResponseToDomainUserModel(newTokenResponse);
4509
+ }
4510
+ return null;
4511
+ }, PassportErrorType.AUTHENTICATION_ERROR);
4512
+ }
4513
+ async refreshToken(refreshToken) {
4514
+ const response = await axios$1.post(`${this.config.authenticationDomain}/oauth/token`, {
4515
+ client_id: this.config.oidcConfiguration.clientId,
4516
+ grant_type: 'refresh_token',
4517
+ refresh_token: refreshToken,
4518
+ }, formUrlEncodedHeader);
4519
+ return response.data;
4520
+ }
4364
4521
  async logout() {
4365
4522
  return withPassportError(async () => this.userManager.signoutRedirect(), PassportErrorType.LOGOUT_ERROR);
4366
4523
  }
4524
+ async logoutDeviceFlow() {
4525
+ return withPassportError(async () => {
4526
+ this.deviceCredentialsManager.clearCredentials();
4527
+ }, PassportErrorType.LOGOUT_ERROR);
4528
+ }
4367
4529
  async loginSilent() {
4368
4530
  return withPassportError(async () => {
4369
4531
  const existedUser = await this.getUser();
@@ -4380,12 +4542,28 @@ class AuthManager {
4380
4542
  async getUser() {
4381
4543
  return withPassportError(async () => {
4382
4544
  const oidcUser = await this.userManager.getUser();
4383
- if (!oidcUser) {
4384
- return null;
4545
+ if (oidcUser) {
4546
+ return AuthManager.mapOidcUserToDomainModel(oidcUser);
4385
4547
  }
4386
- return AuthManager.mapOidcUserToDomainModel(oidcUser);
4548
+ const deviceToken = this.deviceCredentialsManager.getCredentials();
4549
+ if (deviceToken) {
4550
+ return AuthManager.mapDeviceTokenResponseToDomainUserModel(deviceToken);
4551
+ }
4552
+ return null;
4387
4553
  }, PassportErrorType.NOT_LOGGED_IN_ERROR);
4388
4554
  }
4555
+ async getUserDeviceFlow() {
4556
+ return withPassportError(async () => {
4557
+ const deviceToken = this.deviceCredentialsManager.getCredentials();
4558
+ if (deviceToken) {
4559
+ return AuthManager.mapDeviceTokenResponseToDomainUserModel(deviceToken);
4560
+ }
4561
+ return null;
4562
+ }, PassportErrorType.NOT_LOGGED_IN_ERROR);
4563
+ }
4564
+ checkStoredDeviceFlowCredentials() {
4565
+ return this.deviceCredentialsManager.getCredentials();
4566
+ }
4389
4567
  }
4390
4568
 
4391
4569
  class MagicAdapter {
@@ -4913,6 +5091,17 @@ class PassportImxProviderFactory {
4913
5091
  }
4914
5092
  return this.createProviderInstance(user);
4915
5093
  }
5094
+ async getProviderWithDeviceFlow(deviceCode, interval, timeoutMs) {
5095
+ const user = await this.authManager.connectImxDeviceFlow(deviceCode, interval, timeoutMs);
5096
+ return this.createProviderInstance(user);
5097
+ }
5098
+ async getProviderWithCredentials(tokenResponse) {
5099
+ const user = await this.authManager.connectImxWithCredentials(tokenResponse);
5100
+ if (!user) {
5101
+ return null;
5102
+ }
5103
+ return this.createProviderInstance(user);
5104
+ }
4916
5105
  async createProviderInstance(user) {
4917
5106
  if (!user.idToken) {
4918
5107
  throw new PassportError('Failed to initialise', PassportErrorType.WALLET_CONNECTION_ERROR);
@@ -5220,16 +5409,38 @@ class Passport {
5220
5409
  async connectImx() {
5221
5410
  return this.passportImxProviderFactory.getProvider();
5222
5411
  }
5412
+ async loginWithDeviceFlow() {
5413
+ return this.authManager.loginWithDeviceFlow();
5414
+ }
5415
+ async connectImxDeviceFlow(deviceCode, interval, timeoutMs) {
5416
+ return this.passportImxProviderFactory.getProviderWithDeviceFlow(deviceCode, interval, timeoutMs);
5417
+ }
5418
+ /**
5419
+ * @returns {boolean} the stored device flow credentials if they exist
5420
+ */
5421
+ checkStoredDeviceFlowCredentials() {
5422
+ return this.authManager.checkStoredDeviceFlowCredentials();
5423
+ }
5424
+ async connectImxWithCredentials(tokenResponse) {
5425
+ return this.passportImxProviderFactory.getProviderWithCredentials(tokenResponse);
5426
+ }
5223
5427
  async loginCallback() {
5224
5428
  return this.authManager.loginCallback();
5225
5429
  }
5226
5430
  async logout() {
5227
5431
  return this.authManager.logout();
5228
5432
  }
5433
+ async logoutDeviceFlow() {
5434
+ return this.authManager.logoutDeviceFlow();
5435
+ }
5229
5436
  async getUserInfo() {
5230
5437
  const user = await this.authManager.getUser();
5231
5438
  return user?.profile;
5232
5439
  }
5440
+ async getUserInfoDeviceFlow() {
5441
+ const user = await this.authManager.getUserDeviceFlow();
5442
+ return user?.profile;
5443
+ }
5233
5444
  async getIdToken() {
5234
5445
  const user = await this.authManager.getUser();
5235
5446
  return user?.idToken;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@imtbl/sdk",
3
3
  "description": "Immutable Unified SDK",
4
- "version": "0.1.10-alpha.4",
4
+ "version": "0.2.1-alpha",
5
5
  "author": "Immutable",
6
6
  "bugs": "https://github.com/immutable/ts-immutable-sdk/issues",
7
7
  "dependencies": {
@@ -110,6 +110,7 @@
110
110
  "main": "./dist/index.cjs",
111
111
  "module": "./dist/index.js",
112
112
  "peerDependencies": {
113
+ "ethers": "^5.7.2",
113
114
  "react": "^18.2.0",
114
115
  "react-dom": "^18.2.0"
115
116
  },