diff --git a/CHANGELOG.md b/CHANGELOG.md index 07d5f525..59b77761 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.0.0] - 2024-02-26 + +### Added + +- `completeWithdrawal` has been updated to use V2 withdrawal logic for StarkEx V4 contract. +- [BREAKING CHANGE] `completeWithdrawal` now requires `WalletConnection` instead of Eth Signer. + + ## [2.6.1] - 2024-02-26 ### Added diff --git a/examples/completeErc20Withdrawal.ts b/examples/completeErc20Withdrawal.ts index d82cb1d1..1e60c054 100644 --- a/examples/completeErc20Withdrawal.ts +++ b/examples/completeErc20Withdrawal.ts @@ -7,11 +7,8 @@ import { generateWalletConnection } from './libs/walletConnection'; const client = new ImmutableX(Config.SANDBOX); - const starkPublicKey = await walletConnection.starkSigner.getAddress(); - const completeWithdrawalResponse = await client.completeWithdrawal( - walletConnection.ethSigner, - starkPublicKey, + walletConnection, { type: 'ERC20', tokenAddress: '', diff --git a/examples/completeEthWithdrawal.ts b/examples/completeEthWithdrawal.ts index 3fb91384..eb416cd0 100644 --- a/examples/completeEthWithdrawal.ts +++ b/examples/completeEthWithdrawal.ts @@ -7,11 +7,8 @@ import { generateWalletConnection } from './libs/walletConnection'; const client = new ImmutableX(Config.SANDBOX); - const starkPublicKey = await walletConnection.starkSigner.getAddress(); - const completeWithdrawalResponse = await client.completeWithdrawal( - walletConnection.ethSigner, - starkPublicKey, + walletConnection, { type: 'ETH', }, diff --git a/examples/completeNftWithdrawal.ts b/examples/completeNftWithdrawal.ts index 29550bb1..04eeff8f 100644 --- a/examples/completeNftWithdrawal.ts +++ b/examples/completeNftWithdrawal.ts @@ -7,11 +7,8 @@ import { generateWalletConnection } from './libs/walletConnection'; const client = new ImmutableX(Config.SANDBOX); - const starkPublicKey = await walletConnection.starkSigner.getAddress(); - const completeWithdrawalResponse = await client.completeWithdrawal( - walletConnection.ethSigner, - starkPublicKey, + walletConnection, { type: 'ERC721', tokenAddress: '', diff --git a/package.json b/package.json index db66a5c2..19331b64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@imtbl/core-sdk", - "version": "2.6.1", + "version": "3.0.0", "description": "Immutable Core SDK", "main": "dist/index.cjs.js", "module": "dist/index.es.js", diff --git a/src/ImmutableX.ts b/src/ImmutableX.ts index 0e446916..1dbcf4d2 100644 --- a/src/ImmutableX.ts +++ b/src/ImmutableX.ts @@ -592,19 +592,17 @@ export class ImmutableX { /** * Completes a Withdrawal - * @param ethSigner - the L1 signer - * @param starkPublicKey - the Signer address + * @param walletConnection - the pair of L1/L2 signers * @param token - the token * @returns a promise that resolves with the transaction * @throws {@link index.IMXError} */ public completeWithdrawal( - ethSigner: EthSigner, - starkPublicKey: string, + walletConnection: WalletConnection, token: AnyToken, ) { return this.workflows - .completeWithdrawal(ethSigner, starkPublicKey, token) + .completeWithdrawal(walletConnection, token) .catch(err => { throw formatError(err); }); diff --git a/src/workflows/withdrawal/completeERC20Withdrawal.ts b/src/workflows/withdrawal/completeERC20Withdrawal.ts index a92083a3..40aed016 100644 --- a/src/workflows/withdrawal/completeERC20Withdrawal.ts +++ b/src/workflows/withdrawal/completeERC20Withdrawal.ts @@ -7,6 +7,7 @@ import { StarkV3__factory, Registration, Registration__factory, + StarkV4__factory, } from '../../contracts'; import { ERC20Token } from '../../types'; import { @@ -55,14 +56,14 @@ async function executeWithdrawERC20( return signer.sendTransaction(populatedTransaction); } -export async function completeERC20WithdrawalWorkflow( +export async function completeERC20WithdrawalV1Workflow( signer: Signer, starkPublicKey: string, token: ERC20Token, encodingApi: EncodingApi, usersApi: UsersApi, config: ImmutableXConfiguration, -) { +): Promise { const assetType = await getEncodeAssetInfo('asset', 'ERC20', encodingApi, { token_address: token.tokenAddress, }); @@ -99,3 +100,28 @@ export async function completeERC20WithdrawalWorkflow( ); } } + +export async function completeERC20WithdrawalV2Workflow( + signer: Signer, + token: ERC20Token, + encodingApi: EncodingApi, + config: ImmutableXConfiguration, +): Promise { + const assetType = await getEncodeAssetInfo('asset', 'ERC20', encodingApi, { + token_address: token.tokenAddress, + }); + + const coreContract = StarkV4__factory.connect( + config.ethConfiguration.coreContractAddress, + signer, + ); + + const ownerKey = await signer.getAddress(); + + const populatedTransaction = await coreContract.populateTransaction.withdraw( + ownerKey, + assetType.asset_type, + ); + + return signer.sendTransaction(populatedTransaction); +} diff --git a/src/workflows/withdrawal/completeERC721Withdrawal.ts b/src/workflows/withdrawal/completeERC721Withdrawal.ts index bf7a0b38..f4fb2927 100644 --- a/src/workflows/withdrawal/completeERC721Withdrawal.ts +++ b/src/workflows/withdrawal/completeERC721Withdrawal.ts @@ -5,6 +5,7 @@ import { StarkV3__factory, Registration, Registration__factory, + StarkV4__factory, } from '../../contracts'; import * as encUtils from 'enc-utils'; import { ERC721Token } from '../../types'; @@ -75,7 +76,7 @@ function getMintingBlob(token: MintableERC721Withdrawal): string { return encUtils.sanitizeHex(encUtils.utf8ToHex(`{${id}}:{${blueprint}}`)); } -async function completeMintableERC721Withdrawal( +async function completeMintableERC721WithdrawalV1( signer: Signer, starkPublicKey: string, token: MintableERC721Withdrawal, @@ -173,7 +174,7 @@ async function executeWithdrawERC721( return signer.sendTransaction(populatedTransaction); } -async function completeERC721Withdrawal( +async function completeERC721WithdrawalV1( signer: Signer, starkPublicKey: string, token: ERC721Token, @@ -221,7 +222,7 @@ async function completeERC721Withdrawal( } } -export async function completeERC721WithdrawalWorkflow( +export async function completeERC721WithdrawalV1Workflow( signer: Signer, starkPublicKey: string, token: ERC721Token, @@ -229,7 +230,7 @@ export async function completeERC721WithdrawalWorkflow( mintsApi: MintsApi, usersApi: UsersApi, config: ImmutableXConfiguration, -) { +): Promise { const tokenAddress = token.tokenAddress; const tokenId = token.tokenId; return await mintsApi @@ -238,7 +239,7 @@ export async function completeERC721WithdrawalWorkflow( tokenId, }) .then(mintableToken => - completeMintableERC721Withdrawal( + completeMintableERC721WithdrawalV1( signer, starkPublicKey, { @@ -257,7 +258,7 @@ export async function completeERC721WithdrawalWorkflow( .catch(error => { if (error.response?.status === 404) { // token is already minted on L1 - return completeERC721Withdrawal( + return completeERC721WithdrawalV1( signer, starkPublicKey, token, @@ -269,3 +270,108 @@ export async function completeERC721WithdrawalWorkflow( throw error; // unable to recover from any other kind of error }); } + +async function completeMintableERC721WithdrawalV2( + signer: Signer, + ownerKey: string, + token: MintableERC721Withdrawal, + encodingApi: EncodingApi, + config: ImmutableXConfiguration, +) { + const assetType = await getEncodeAssetInfo( + 'mintable-asset', + 'ERC721', + encodingApi, + { + id: token.data.id, + token_address: token.data.tokenAddress, + ...(token.data.blueprint && { blueprint: token.data.blueprint }), + }, + ); + const mintingBlob = getMintingBlob(token); + + const coreContract = StarkV4__factory.connect( + config.ethConfiguration.coreContractAddress, + signer, + ); + + const populatedTransaction = + await coreContract.populateTransaction.withdrawAndMint( + ownerKey, + assetType.asset_type, + mintingBlob, + ); + return signer.sendTransaction(populatedTransaction); +} + +async function completeERC721WithdrawalV2( + signer: Signer, + ownerKey: string, + token: ERC721Token, + encodingApi: EncodingApi, + config: ImmutableXConfiguration, +) { + const assetType = await getEncodeAssetInfo('asset', 'ERC721', encodingApi, { + token_id: token.tokenId, + token_address: token.tokenAddress, + }); + + const coreContract = StarkV4__factory.connect( + config.ethConfiguration.coreContractAddress, + signer, + ); + + const populatedTransaction = + await coreContract.populateTransaction.withdrawNft( + ownerKey, + assetType.asset_type, + token.tokenId, + ); + return signer.sendTransaction(populatedTransaction); +} + +export async function completeERC721WithdrawalV2Workflow( + signer: Signer, + ownerKey: string, + token: ERC721Token, + encodingApi: EncodingApi, + mintsApi: MintsApi, + config: ImmutableXConfiguration, +): Promise { + const tokenAddress = token.tokenAddress; + const tokenId = token.tokenId; + return await mintsApi + .getMintableTokenDetailsByClientTokenId({ + tokenAddress, + tokenId, + }) + .then(mintableToken => + completeMintableERC721WithdrawalV2( + signer, + ownerKey, + { + type: 'ERC721', + data: { + id: tokenId, + tokenAddress: tokenAddress, + blueprint: mintableToken.data.blueprint, + }, + }, + encodingApi, + config, + ), + ) + .catch(error => { + if (error.response?.status === 404) { + // token is already minted on L1 + return completeERC721WithdrawalV2( + signer, + ownerKey, + token, + encodingApi, + config, + ); + } + throw error; // unable to recover from any other kind of error + }); +} diff --git a/src/workflows/withdrawal/completeEthWithdrawal.ts b/src/workflows/withdrawal/completeEthWithdrawal.ts index e906ba86..3abe7b8c 100644 --- a/src/workflows/withdrawal/completeEthWithdrawal.ts +++ b/src/workflows/withdrawal/completeEthWithdrawal.ts @@ -7,6 +7,7 @@ import { StarkV3__factory, Registration, Registration__factory, + StarkV4__factory, } from '../../contracts'; import { getSignableRegistrationOnchain, @@ -54,13 +55,13 @@ async function executeWithdrawEth( return signer.sendTransaction(populatedTransaction); } -export async function completeEthWithdrawalWorkflow( +export async function completeEthWithdrawalV1Workflow( signer: Signer, starkPublicKey: string, encodingApi: EncodingApi, usersApi: UsersApi, config: ImmutableXConfiguration, -) { +): Promise { const assetType = await getEncodeAssetInfo('asset', 'ETH', encodingApi); const coreContract = StarkV3__factory.connect( @@ -95,3 +96,25 @@ export async function completeEthWithdrawalWorkflow( ); } } + +export async function completeEthWithdrawalV2Workflow( + signer: Signer, + encodingApi: EncodingApi, + config: ImmutableXConfiguration, +): Promise { + const assetType = await getEncodeAssetInfo('asset', 'ETH', encodingApi); + + const coreContract = StarkV4__factory.connect( + config.ethConfiguration.coreContractAddress, + signer, + ); + + const ownerKey = await signer.getAddress(); + + const populatedTransaction = await coreContract.populateTransaction.withdraw( + ownerKey, + assetType.asset_type, + ); + + return signer.sendTransaction(populatedTransaction); +} diff --git a/src/workflows/withdrawal/completeWithdrawal.ts b/src/workflows/withdrawal/completeWithdrawal.ts new file mode 100644 index 00000000..e2ec18db --- /dev/null +++ b/src/workflows/withdrawal/completeWithdrawal.ts @@ -0,0 +1,87 @@ +import { Signer } from '@ethersproject/abstract-signer'; +import { TransactionResponse } from '@ethersproject/providers'; +import { AnyToken } from 'src/types'; +import { + completeEthWithdrawalV1Workflow, + completeEthWithdrawalV2Workflow, +} from './completeEthWithdrawal'; +import { EncodingApi, MintsApi, UsersApi } from 'src/api'; +import { ImmutableXConfiguration } from 'src/config'; +import { + completeERC20WithdrawalV1Workflow, + completeERC20WithdrawalV2Workflow, +} from './completeERC20Withdrawal'; +import { + completeERC721WithdrawalV1Workflow, + completeERC721WithdrawalV2Workflow, +} from './completeERC721Withdrawal'; + +export async function completeWithdrawalV1Workflow( + signer: Signer, + starkPublicKey: string, + token: AnyToken, + encodingApi: EncodingApi, + usersApi: UsersApi, + mintsApi: MintsApi, + config: ImmutableXConfiguration, +): Promise { + switch (token.type) { + case 'ETH': + return completeEthWithdrawalV1Workflow( + signer, + starkPublicKey, + encodingApi, + usersApi, + config, + ); + case 'ERC20': + return completeERC20WithdrawalV1Workflow( + signer, + starkPublicKey, + token, + encodingApi, + usersApi, + config, + ); + case 'ERC721': + return completeERC721WithdrawalV1Workflow( + signer, + starkPublicKey, + token, + encodingApi, + mintsApi, + usersApi, + config, + ); + } +} + +export async function completeWithdrawalV2Workflow( + signer: Signer, + ownerKey: string, + token: AnyToken, + encodingApi: EncodingApi, + mintsApi: MintsApi, + config: ImmutableXConfiguration, +): Promise { + switch (token.type) { + case 'ETH': + return completeEthWithdrawalV2Workflow(signer, encodingApi, config); + case 'ERC20': + return completeERC20WithdrawalV2Workflow( + signer, + token, + encodingApi, + config, + ); + case 'ERC721': + return completeERC721WithdrawalV2Workflow( + signer, + ownerKey, + token, + encodingApi, + mintsApi, + config, + ); + } +} diff --git a/src/workflows/workflows.ts b/src/workflows/workflows.ts index df59a65f..c336098e 100644 --- a/src/workflows/workflows.ts +++ b/src/workflows/workflows.ts @@ -1,4 +1,3 @@ -import { Signer } from '@ethersproject/abstract-signer'; import { DepositsApi, EncodingApi, @@ -35,7 +34,6 @@ import { ETHAmount, ERC20Amount, AnyToken, - ERC20Token, EthSigner, UnsignedExchangeTransferRequest, StarkExContractVersion, @@ -53,9 +51,6 @@ import { depositEthWorkflow, } from './deposit'; import { - completeERC20WithdrawalWorkflow, - completeERC721WithdrawalWorkflow, - completeEthWithdrawalWorkflow, prepareWithdrawalV2Workflow, prepareWithdrawalWorkflow, } from './withdrawal'; @@ -65,11 +60,17 @@ import { generateIMXAuthorisationHeaders } from '../utils'; import { ImmutableXConfiguration } from '../config'; import { exchangeTransfersWorkflow } from './exchangeTransfers'; import axios, { AxiosResponse } from 'axios'; +import { Signer } from '@ethersproject/abstract-signer'; import { CreatePrimarySaleWorkflow, AcceptPrimarySalesWorkflow, RejectPrimarySalesWorkflow, } from './primarySales'; +import { TransactionResponse } from '@ethersproject/providers'; +import { + completeWithdrawalV1Workflow, + completeWithdrawalV2Workflow, +} from './withdrawal/completeWithdrawal'; export class Workflows { private readonly depositsApi: DepositsApi; @@ -287,66 +288,43 @@ export class Workflows { return parseInt(contractVersion.charAt(0)); } - public completeWithdrawal( - signer: Signer, - starkPublicKey: string, + public async completeWithdrawal( + walletConnection: WalletConnection, token: AnyToken, - ) { - switch (token.type) { - case 'ETH': - return this.completeEthWithdrawal(signer, starkPublicKey); - case 'ERC20': - return this.completeERC20Withdrawal(signer, starkPublicKey, token); - case 'ERC721': - return this.completeERC721Withdrawal(signer, starkPublicKey, token); - } - } - - private async completeEthWithdrawal(signer: Signer, starkPublicKey: string) { - await this.validateChain(signer); - - return completeEthWithdrawalWorkflow( - signer, - starkPublicKey, - this.encodingApi, - this.usersApi, - this.config, - ); - } - - private async completeERC20Withdrawal( - signer: Signer, - starkPublicKey: string, - token: ERC20Token, - ) { - await this.validateChain(signer); + ): Promise { + await this.validateChain(walletConnection.ethSigner); - return completeERC20WithdrawalWorkflow( - signer, - starkPublicKey, - token, - this.encodingApi, - this.usersApi, - this.config, + const starkExContractInfo = await this.getStarkExContractVersion(); + const majorContractVersion = await this.parseMajorContractVersion( + starkExContractInfo.data.version, ); - } - - private async completeERC721Withdrawal( - signer: Signer, - starkPublicKey: string, - token: ERC721Token, - ) { - await this.validateChain(signer); - return completeERC721WithdrawalWorkflow( - signer, - starkPublicKey, - token, - this.encodingApi, - this.mintsApi, - this.usersApi, - this.config, - ); + if (majorContractVersion === 3) { + const starkPublicKey = await walletConnection.starkSigner.getAddress(); + return completeWithdrawalV1Workflow( + walletConnection.ethSigner, + starkPublicKey, + token, + this.encodingApi, + this.usersApi, + this.mintsApi, + this.config, + ); + } else if (majorContractVersion >= 4) { + const ethAddress = await walletConnection.ethSigner.getAddress(); + return completeWithdrawalV2Workflow( + walletConnection.ethSigner, + ethAddress, + token, + this.encodingApi, + this.mintsApi, + this.config, + ); + } else { + throw new Error( + `Invalid StarkEx contract version (${majorContractVersion}). Please try again later.`, + ); + } } public async createOrder(