diff --git a/openapi.json b/openapi.json index 4247b71a..65c6b705 100644 --- a/openapi.json +++ b/openapi.json @@ -1733,6 +1733,63 @@ } } }, + "/v1/primary_sales/signable-primary-sale-details": { + "post": { + "description": "[Experimental] Signable Create Primary Sale. This endpoint is experimental and may change in the future.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "primary-sales" + ], + "summary": "[Experimental] Signable Create Primary Sale", + "operationId": "SignableCreatePrimarySale", + "parameters": [ + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/signableCreatePrimarySaleParamsBody" + } + } + ], + "responses": { + "200": { + "description": "OK response.", + "schema": { + "$ref": "#/definitions/signableCreatePrimarySaleOKBody" + } + }, + "400": { + "description": "Bad Request (400)", + "schema": { + "$ref": "#/definitions/signableCreatePrimarySaleBadRequestBody" + } + }, + "404": { + "description": "The specified resource was not found (404)", + "schema": { + "$ref": "#/definitions/signableCreatePrimarySaleNotFoundBody" + } + }, + "500": { + "description": "Internal Server Error (500)", + "schema": { + "$ref": "#/definitions/signableCreatePrimarySaleInternalServerErrorBody" + } + }, + "501": { + "description": "Not Implemented Error (501)", + "schema": { + "$ref": "#/definitions/signableCreatePrimarySaleNotImplementedBody" + } + } + } + } + }, "/v1/primary_sales/{id}": { "get": { "description": "[Experimental] Get a single primary sale by ID. This endpoint is experimental and may change in the future.", @@ -2179,63 +2236,6 @@ } } }, - "/v1/signable-primary-sale-details": { - "post": { - "description": "[Experimental] Signable Create Primary Sale. This endpoint is experimental and may change in the future.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "primary-sales" - ], - "summary": "[Experimental] Signable Create Primary Sale", - "operationId": "SignableCreatePrimarySale", - "parameters": [ - { - "name": "body", - "in": "body", - "schema": { - "$ref": "#/definitions/signableCreatePrimarySaleParamsBody" - } - } - ], - "responses": { - "200": { - "description": "OK response.", - "schema": { - "$ref": "#/definitions/signableCreatePrimarySaleOKBody" - } - }, - "400": { - "description": "Bad Request (400)", - "schema": { - "$ref": "#/definitions/signableCreatePrimarySaleBadRequestBody" - } - }, - "404": { - "description": "The specified resource was not found (404)", - "schema": { - "$ref": "#/definitions/signableCreatePrimarySaleNotFoundBody" - } - }, - "500": { - "description": "Internal Server Error (500)", - "schema": { - "$ref": "#/definitions/signableCreatePrimarySaleInternalServerErrorBody" - } - }, - "501": { - "description": "Not Implemented Error (501)", - "schema": { - "$ref": "#/definitions/signableCreatePrimarySaleNotImplementedBody" - } - } - } - } - }, "/v1/signable-registration": { "post": { "description": "Get operator signature to allow clients to register the user", @@ -8846,6 +8846,7 @@ "INVALID", "IN_PROGRESS", "ACCEPTED", + "FAILED", "REJECTED", "EXPIRED" ] @@ -9488,6 +9489,7 @@ "INVALID", "IN_PROGRESS", "ACCEPTED", + "FAILED", "REJECTED", "EXPIRED" ] @@ -11428,6 +11430,7 @@ "INVALID", "IN_PROGRESS", "ACCEPTED", + "FAILED", "REJECTED", "EXPIRED" ] diff --git a/src/ImmutableX.ts b/src/ImmutableX.ts index cc135ef7..fb3e891a 100644 --- a/src/ImmutableX.ts +++ b/src/ImmutableX.ts @@ -48,6 +48,7 @@ import { OrdersApi, OrdersApiGetOrderV3Request, OrdersApiListOrdersV3Request, + PrimarySalesApiSignableCreatePrimarySaleRequest, ProjectsApi, TokensApi, TokensApiGetTokenRequest, @@ -951,4 +952,22 @@ export class ImmutableX { throw formatError(err); }); } + + /** + * Create a PrimarySale + * @param walletConnection - the pair of L1/L2 signers + * @param request - the request object to be provided in the API request + * @returns a promise that resolves with the created Trade + * @throws {@link index.IMXError} + */ + public createPrimarySale( + walletConnection: WalletConnection, + request: PrimarySalesApiSignableCreatePrimarySaleRequest, + ) { + return this.workflows + .createPrimarySale(walletConnection, request) + .catch(err => { + throw formatError(err); + }); + } } diff --git a/src/api/domain/primary-sales-api.ts b/src/api/domain/primary-sales-api.ts index 0c19f7c1..49f1f1a9 100644 --- a/src/api/domain/primary-sales-api.ts +++ b/src/api/domain/primary-sales-api.ts @@ -248,7 +248,7 @@ export const PrimarySalesApiAxiosParamCreator = function (configuration?: Config * @throws {RequiredError} */ signableCreatePrimarySale: async (body?: SignableCreatePrimarySaleParamsBody, options: AxiosRequestConfig = {}): Promise => { - const localVarPath = `/v1/signable-primary-sale-details`; + const localVarPath = `/v1/primary_sales/signable-primary-sale-details`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; diff --git a/src/api/models/accept-primary-sale-okbody-result.ts b/src/api/models/accept-primary-sale-okbody-result.ts index 13fc4409..bc3be4d2 100644 --- a/src/api/models/accept-primary-sale-okbody-result.ts +++ b/src/api/models/accept-primary-sale-okbody-result.ts @@ -109,6 +109,7 @@ export const AcceptPrimarySaleOKBodyResultStatusEnum = { Invalid: 'INVALID', InProgress: 'IN_PROGRESS', Accepted: 'ACCEPTED', + Failed: 'FAILED', Rejected: 'REJECTED', Expired: 'EXPIRED' } as const; diff --git a/src/api/models/create-primary-sale-created-body-result.ts b/src/api/models/create-primary-sale-created-body-result.ts index 101c7fbd..3ab8b519 100644 --- a/src/api/models/create-primary-sale-created-body-result.ts +++ b/src/api/models/create-primary-sale-created-body-result.ts @@ -109,6 +109,7 @@ export const CreatePrimarySaleCreatedBodyResultStatusEnum = { Invalid: 'INVALID', InProgress: 'IN_PROGRESS', Accepted: 'ACCEPTED', + Failed: 'FAILED', Rejected: 'REJECTED', Expired: 'EXPIRED' } as const; diff --git a/src/api/models/get-primary-sale-okbody-result.ts b/src/api/models/get-primary-sale-okbody-result.ts index 1e388987..c48558db 100644 --- a/src/api/models/get-primary-sale-okbody-result.ts +++ b/src/api/models/get-primary-sale-okbody-result.ts @@ -109,6 +109,7 @@ export const GetPrimarySaleOKBodyResultStatusEnum = { Invalid: 'INVALID', InProgress: 'IN_PROGRESS', Accepted: 'ACCEPTED', + Failed: 'FAILED', Rejected: 'REJECTED', Expired: 'EXPIRED' } as const; diff --git a/src/workflows/primarySales.ts b/src/workflows/primarySales.ts new file mode 100644 index 00000000..c16505bd --- /dev/null +++ b/src/workflows/primarySales.ts @@ -0,0 +1,79 @@ +import { + CreatePrimarySaleBadRequestBody, + CreatePrimarySaleCreatedBody, + CreatePrimarySaleForbiddenBody, + CreatePrimarySaleNotFoundBody, + CreatePrimarySaleUnauthorizedBody, + PrimarySalesApi, + PrimarySalesApiCreatePrimarySaleRequest, + PrimarySalesApiSignableCreatePrimarySaleRequest, +} from '../api'; +import { WalletConnection } from '../types'; +import { signRaw } from '../utils'; + +type CreatePrimarySaleWorkflowParams = WalletConnection & { + request: PrimarySalesApiSignableCreatePrimarySaleRequest; + primarySalesApi: PrimarySalesApi; +}; + +type CreatePrimarySaleResponse = + | CreatePrimarySaleBadRequestBody + | CreatePrimarySaleCreatedBody + | CreatePrimarySaleForbiddenBody + | CreatePrimarySaleUnauthorizedBody + | CreatePrimarySaleNotFoundBody; + +export async function CreatePrimarySaleWorkflow({ + ethSigner, + starkSigner, + request, + primarySalesApi, +}: CreatePrimarySaleWorkflowParams): Promise { + const ethAddress = await ethSigner.getAddress(); + + const signablePrimarySaleResponse = + await primarySalesApi.signableCreatePrimarySale(request); + + const { signable_message: signableMessage, payload_hash: payloadHash } = + signablePrimarySaleResponse.data; + + const ethSignature = await signRaw(signableMessage, ethSigner); + + const starkSignature = await starkSigner.signMessage(payloadHash); + + const resp = signablePrimarySaleResponse.data; + + const primarySaleParams: PrimarySalesApiCreatePrimarySaleRequest = { + body: { + buyer_ether_key: resp.buyer_ether_key, + buyer_stark_key: resp.buyer_stark_key, + buyer_vault_id: resp.buyer_vault_id, + studio_ether_key: resp.studio_ether_key, + studio_data: resp.studio_data, + payment_amount: resp.payment_amount, + payment_asset_id: resp.payment_asset_id, + payment_recipient_ether_key: resp.payment_recipient_ether_key, + payment_recipient_stark_key: resp.payment_recipient_stark_key, + payment_recipient_vault_id: resp.payment_recipient_vault_id, + items_recipient_ether_key: resp.items_recipient_ether_key, + expiration_timestamp: resp.expiration_timestamp, + fees: resp.fees, + nonce: resp.nonce, + stark_signature: starkSignature, + }, + }; + + const createPrimarySaleResp = await primarySalesApi.createPrimarySale( + primarySaleParams, + { + headers: { + 'x-imx-eth-address': ethAddress, + 'x-imx-eth-signature': ethSignature, + }, + }, + ); + + return { + ...createPrimarySaleResp.data, + }; +} diff --git a/src/workflows/workflows.ts b/src/workflows/workflows.ts index 99eb2dd4..355fbaf3 100644 --- a/src/workflows/workflows.ts +++ b/src/workflows/workflows.ts @@ -21,6 +21,8 @@ import { MetadataRefreshesApi, CreateMetadataRefreshRequest, ExchangesApi, + PrimarySalesApiSignableCreatePrimarySaleRequest, + PrimarySalesApi, } from '../api'; import { UnsignedMintRequest, @@ -62,6 +64,7 @@ import { generateIMXAuthorisationHeaders } from '../utils'; import { ImmutableXConfiguration } from '../config'; import { exchangeTransfersWorkflow } from './exchangeTransfers'; import axios, { AxiosResponse } from 'axios'; +import { CreatePrimarySaleWorkflow } from './primarySales'; export class Workflows { private readonly depositsApi: DepositsApi; @@ -78,6 +81,7 @@ export class Workflows { private readonly metadataApi: MetadataApi; private readonly metadataRefreshesApi: MetadataRefreshesApi; private readonly exchangesApi: ExchangesApi; + private readonly primarySalesApi: PrimarySalesApi; private isChainValid(chainID: number) { return chainID === this.config.ethConfiguration.chainID; @@ -101,6 +105,7 @@ export class Workflows { this.metadataApi = new MetadataApi(apiConfiguration); this.metadataRefreshesApi = new MetadataRefreshesApi(apiConfiguration); this.exchangesApi = new ExchangesApi(apiConfiguration); + this.primarySalesApi = new PrimarySalesApi(apiConfiguration); } private async validateChain(signer: Signer) { @@ -509,4 +514,17 @@ export class Workflows { createMetadataRefreshRequest: request, }); } + + public async createPrimarySale( + walletConnection: WalletConnection, + request: PrimarySalesApiSignableCreatePrimarySaleRequest, + ) { + await this.validateChain(walletConnection.ethSigner); + + return CreatePrimarySaleWorkflow({ + ...walletConnection, + request, + primarySalesApi: this.primarySalesApi, + }); + } }