diff --git a/.yarn/versions/bf801d06.yml b/.yarn/versions/bf801d06.yml new file mode 100644 index 00000000..8b4cb19c --- /dev/null +++ b/.yarn/versions/bf801d06.yml @@ -0,0 +1,7 @@ +releases: + "@near-eth/aurora-erc20": patch + "@near-eth/aurora-ether": patch + "@near-eth/near-ether": patch + "@near-eth/nep141-erc20": patch + "@near-eth/rainbow": patch + rainbow-bridge-client-monorepo: patch diff --git a/packages/aurora-erc20/src/bridged-erc20/sendToEthereum/index.ts b/packages/aurora-erc20/src/bridged-erc20/sendToEthereum/index.ts index 65b87fa2..37c074dc 100644 --- a/packages/aurora-erc20/src/bridged-erc20/sendToEthereum/index.ts +++ b/packages/aurora-erc20/src/bridged-erc20/sendToEthereum/index.ts @@ -196,42 +196,50 @@ export async function checkStatus (transfer: Transfer): Promise { * Parse the burn receipt id and block height needed to complete * the step BURN * @param nearBurnTx + * @param nep141Factory * @param nearProvider */ export async function parseBurnReceipt ( nearBurnTx: FinalExecutionOutcome, + nep141Factory: string, nearProvider: najProviders.Provider -): Promise<{id: string, blockHeight: number }> { - const receiptIds = nearBurnTx.transaction_outcome.outcome.receipt_ids - - if (receiptIds.length !== 1) { - throw new TransferError( - `Burn expects only one receipt, got ${receiptIds.length}. - Full withdrawal transaction: ${JSON.stringify(nearBurnTx)}` - ) +): Promise<{id: string, blockHeight: number, blockTimestamp: number, event: { amount: string, token: string, recipient: string }}> { + // @ts-expect-error + const bridgeReceipt: any = nearBurnTx.receipts_outcome.find(r => r.outcome.executor_id === nep141Factory) + if (!bridgeReceipt) { + throw new Error(`Failed to parse bridge receipt for ${JSON.stringify(nearBurnTx)}`) + } + const successValue = bridgeReceipt.outcome.status.SuccessValue + // eslint-disable-next-line @typescript-eslint/no-extraneous-class + class BurnEvent { + constructor (args: any) { + Object.assign(this, args) + } + } + const SCHEMA = new Map([ + [BurnEvent, { + kind: 'struct', + fields: [ + ['flag', 'u8'], + ['amount', 'u128'], + ['token', [20]], + ['recipient', [20]] + ] + }] + ]) + const rawEvent = deserializeBorsh( + SCHEMA, BurnEvent, Buffer.from(successValue, 'base64') + ) as { amount: BN, token: Uint8Array, recipient: Uint8Array } + const event = { + amount: rawEvent.amount.toString(), + token: '0x' + Buffer.from(rawEvent.token).toString('hex'), + recipient: '0x' + Buffer.from(rawEvent.recipient).toString('hex') } - // Get receipt information for recording and building burn proof - const txReceiptId = receiptIds[0] - const successReceiptOutcome = nearBurnTx.receipts_outcome - .find(r => r.id === txReceiptId)! - .outcome - const withdrawReceiptId = successReceiptOutcome.receipt_ids[0] - const withdrawReceiptOutcome = nearBurnTx.receipts_outcome - .find(r => r.id === withdrawReceiptId)! - .outcome - - // @ts-expect-error TODO - const burnReceiptId = withdrawReceiptOutcome.status.SuccessReceiptId - - const txReceiptBlockHash = nearBurnTx.receipts_outcome - .find(r => r.id === burnReceiptId)! - // @ts-expect-error TODO - .block_hash - - const receiptBlock = await nearProvider.block({ blockId: txReceiptBlockHash }) - const receiptBlockHeight = Number(receiptBlock.header.height) - return { id: burnReceiptId, blockHeight: receiptBlockHeight } + const receiptBlock = await nearProvider.block({ blockId: bridgeReceipt.block_hash }) + const blockHeight = Number(receiptBlock.header.height) + const blockTimestamp = Number(receiptBlock.header.timestamp) + return { id: bridgeReceipt.id, blockHeight, blockTimestamp, event } } export async function findAllTransactions ( @@ -349,45 +357,10 @@ export async function recover ( throw new Error(`Withdraw transaction failed: ${burnTxHash}`) } - // Get withdraw event information from successValue - const nearBurnReceipt = await parseBurnReceipt(burnTx, nearProvider) - const burnReceiptOutcome = burnTx.receipts_outcome - .find(r => r.id === nearBurnReceipt.id)! - .outcome - - // @ts-expect-error TODO - const successValue: string = burnReceiptOutcome.status.SuccessValue - if (!successValue) { - throw new Error( - `Invalid burnTx successValue: '${successValue}' - Full withdrawal transaction: ${JSON.stringify(burnTx)}` - ) - } - - // eslint-disable-next-line @typescript-eslint/no-extraneous-class - class BurnEvent { - constructor (args: any) { - Object.assign(this, args) - } - } - const SCHEMA = new Map([ - [BurnEvent, { - kind: 'struct', - fields: [ - ['flag', 'u8'], - ['amount', 'u128'], - ['token', [20]], - ['recipient', [20]] - ] - }] - ]) - const burnEvent = deserializeBorsh( - SCHEMA, BurnEvent, Buffer.from(successValue, 'base64') - ) as { amount: BN, token: Uint8Array, recipient: Uint8Array } + const nep141Factory = options.nep141Factory ?? getBridgeParams().nep141Factory + const nearBurnReceipt = await parseBurnReceipt(burnTx, nep141Factory, nearProvider) - const amount = burnEvent.amount.toString() - const recipient = '0x' + Buffer.from(burnEvent.recipient).toString('hex') - const erc20Address = '0x' + Buffer.from(burnEvent.token).toString('hex') + const { amount, recipient, token: erc20Address } = nearBurnReceipt.event const symbol = options.symbol ?? await getSymbol({ erc20Address, options }) const decimals = options.decimals ?? await getDecimals({ erc20Address, options }) const sourceTokenName = 'a' + symbol @@ -575,6 +548,7 @@ export async function checkBurn ( auroraRelayerAccount?: string nearAccount?: Account nearProvider?: najProviders.Provider + nep141Factory?: string } ): Promise { options = options ?? {} @@ -671,8 +645,9 @@ export async function checkBurn ( } let nearBurnReceipt + const nep141Factory = options.nep141Factory ?? getBridgeParams().nep141Factory try { - nearBurnReceipt = await parseBurnReceipt(nearBurnTx, nearProvider) + nearBurnReceipt = await parseBurnReceipt(nearBurnTx, nep141Factory, nearProvider) } catch (e) { if (e instanceof TransferError) { return { diff --git a/packages/aurora-ether/src/bridged-ether/sendToEthereum/index.ts b/packages/aurora-ether/src/bridged-ether/sendToEthereum/index.ts index 00ac2776..fee1b9f3 100644 --- a/packages/aurora-ether/src/bridged-ether/sendToEthereum/index.ts +++ b/packages/aurora-ether/src/bridged-ether/sendToEthereum/index.ts @@ -191,31 +191,41 @@ export async function checkStatus (transfer: Transfer): Promise { export async function parseBurnReceipt ( nearBurnTx: FinalExecutionOutcome, nearProvider: najProviders.Provider -): Promise<{id: string, blockHeight: number }> { - const receiptIds = nearBurnTx.transaction_outcome.outcome.receipt_ids - - if (receiptIds.length !== 1) { - throw new TransferError( - `Burn expects only one receipt, got ${receiptIds.length}. - Full withdrawal transaction: ${JSON.stringify(nearBurnTx)}` - ) +): Promise<{id: string, blockHeight: number, blockTimestamp: number, event: { amount: string, recipient: string, etherCustodian: string }}> { + const bridgeReceipt: any = nearBurnTx.receipts_outcome[1] + if (!bridgeReceipt) { + throw new Error(`Failed to parse bridge receipt for ${JSON.stringify(nearBurnTx)}`) + } + const successValue = bridgeReceipt.outcome.status.SuccessValue + // eslint-disable-next-line @typescript-eslint/no-extraneous-class + class BurnEvent { + constructor (args: any) { + Object.assign(this, args) + } + } + const SCHEMA = new Map([ + [BurnEvent, { + kind: 'struct', + fields: [ + ['amount', 'u128'], + ['recipient_id', [20]], + ['eth_custodian_address', [20]] + ] + }] + ]) + const rawEvent = deserializeBorsh( + SCHEMA, BurnEvent, Buffer.from(successValue, 'base64') + ) as { amount: BN, recipient_id: Uint8Array, eth_custodian_address: Uint8Array} + const event = { + amount: rawEvent.amount.toString(), + recipient: '0x' + Buffer.from(rawEvent.recipient_id).toString('hex'), + etherCustodian: '0x' + Buffer.from(rawEvent.eth_custodian_address).toString('hex') } - // Get receipt information for recording and building burn proof - const txReceiptId = receiptIds[0] - const successReceiptOutcome = nearBurnTx.receipts_outcome - .find(r => r.id === txReceiptId)! - .outcome - const burnReceiptId = successReceiptOutcome.receipt_ids[0]! - - const txReceiptBlockHash = nearBurnTx.receipts_outcome - .find(r => r.id === burnReceiptId)! - // @ts-expect-error TODO - .block_hash - - const receiptBlock = await nearProvider.block({ blockId: txReceiptBlockHash }) - const receiptBlockHeight = Number(receiptBlock.header.height) - return { id: burnReceiptId, blockHeight: receiptBlockHeight } + const receiptBlock = await nearProvider.block({ blockId: bridgeReceipt.block_hash }) + const blockHeight = Number(receiptBlock.header.height) + const blockTimestamp = Number(receiptBlock.header.timestamp) + return { id: bridgeReceipt.id, blockHeight, blockTimestamp, event } } export async function findAllTransactions ( @@ -318,57 +328,28 @@ export async function recover ( throw new Error(`Withdraw transaction failed: ${nearBurnTxHash}`) } - // eslint-disable-next-line @typescript-eslint/no-extraneous-class - class BurnEvent { - constructor (args: any) { - Object.assign(this, args) - } - } - const SCHEMA = new Map([ - [BurnEvent, { - kind: 'struct', - fields: [ - ['amount', 'u128'], - ['recipient_id', [20]], - ['eth_custodian_address', [20]] - ] - }] - ]) - // @ts-expect-error TODO - const withdrawResult = burnTx.receipts_outcome[1].outcome.status.SuccessValue - const burnEvent = deserializeBorsh( - SCHEMA, BurnEvent, Buffer.from(withdrawResult, 'base64') - ) as { amount: BN, recipient_id: Uint8Array, eth_custodian_address: Uint8Array} - - const amount = burnEvent.amount.toString() - const recipient = '0x' + Buffer.from(burnEvent.recipient_id).toString('hex') - const etherCustodian = Buffer.from(burnEvent.eth_custodian_address).toString('hex') + const nearBurnReceipt = await parseBurnReceipt(burnTx, nearProvider) + const { amount, recipient, etherCustodian } = nearBurnReceipt.event const etherCustodianAddress: string = options.etherCustodianAddress ?? bridgeParams.etherCustodianAddress - if (etherCustodian !== etherCustodianAddress.slice(2).toLowerCase()) { + if (etherCustodian.toLowerCase() !== etherCustodianAddress.toLowerCase()) { throw new Error( - `Unexpected ether custodian: got${etherCustodian}, + `Unexpected ether custodian: got ${etherCustodian}, expected ${etherCustodianAddress}` ) } - const symbol = 'ETH' const destinationTokenName = symbol const decimals = 18 const sourceTokenName = 'a' + symbol const sourceToken = symbol - const nearBurnReceipt = await parseBurnReceipt(burnTx, nearProvider) - - // @ts-expect-error TODO - const txBlock = await nearProvider.block({ blockId: burnTx.transaction_outcome.block_hash }) - // various attributes stored as arrays, to keep history of retries const transfer = { ...transferDraft, id: Math.random().toString().slice(2), - startTime: new Date(txBlock.header.timestamp / 10 ** 6).toISOString(), + startTime: new Date(nearBurnReceipt.blockTimestamp / 10 ** 6).toISOString(), amount, completedStep: BURN, destinationTokenName, @@ -640,14 +621,11 @@ export async function checkBurn ( throw e } - // @ts-expect-error TODO - const txBlock = await nearProvider.block({ blockId: nearBurnTx.transaction_outcome.block_hash }) - return { ...transfer, status: status.IN_PROGRESS, completedStep: BURN, - startTime: new Date(txBlock.header.timestamp / 10 ** 6).toISOString(), + startTime: new Date(nearBurnReceipt.blockTimestamp / 10 ** 6).toISOString(), burnReceipts: [...transfer.burnReceipts, burnReceipt], nearBurnHashes: [...transfer.nearBurnHashes, nearBurnHash], nearBurnReceiptIds: [...transfer.nearBurnReceiptIds, nearBurnReceipt.id], diff --git a/packages/near-ether/src/bridged-ether/sendToEthereum/index.ts b/packages/near-ether/src/bridged-ether/sendToEthereum/index.ts index 74c1573e..e965cadf 100644 --- a/packages/near-ether/src/bridged-ether/sendToEthereum/index.ts +++ b/packages/near-ether/src/bridged-ether/sendToEthereum/index.ts @@ -283,65 +283,29 @@ export async function recover ( throw new Error(`Burn transaction failed: ${burnTxHash}`) } - // Get burn event information from successValue - // @ts-expect-error TODO - const successValue: string = burnTx.status.SuccessValue - if (!successValue) { + const auroraEvmAccount = options.auroraEvmAccount ?? bridgeParams.auroraEvmAccount + const withdrawReceipt = await parseWithdrawReceipt(burnTx, auroraEvmAccount, nearProvider) + + const { amount, recipient, etherCustodian } = withdrawReceipt.event + const etherCustodianAddress: string = options.etherCustodianAddress ?? bridgeParams.etherCustodianAddress + if (etherCustodian.toLowerCase() !== etherCustodianAddress.toLowerCase()) { throw new Error( - `Invalid burnTx successValue: '${successValue}' - Full withdrawal transaction: ${JSON.stringify(burnTx)}` + `Unexpected ether custodian: got ${etherCustodian}, + expected ${etherCustodianAddress}` ) } - - // eslint-disable-next-line @typescript-eslint/no-extraneous-class - class WithdrawEvent { - constructor (args: any) { - Object.assign(this, args) - } - } - const SCHEMA = new Map([ - [WithdrawEvent, { - kind: 'struct', - fields: [ - ['amount', 'u128'], - ['recipient_id', [20]], - ['eth_custodian_address', [20]] - ] - }] - ]) - const withdrawEvent = deserializeBorsh( - SCHEMA, WithdrawEvent, Buffer.from(successValue, 'base64') - ) as { amount: BN, recipient_id: Uint8Array, eth_custodian_address: Uint8Array} - - const amount = withdrawEvent.amount.toString() - const recipient = '0x' + Buffer.from(withdrawEvent.recipient_id).toString('hex') - const etherCustodian = '0x' + Buffer.from(withdrawEvent.eth_custodian_address).toString('hex') - const etherCustodianAddress = options.etherCustodianAddress ?? bridgeParams.etherCustodianAddress - if (etherCustodian !== etherCustodianAddress.toLowerCase()) { - throw new Error('Failed to verify ETH custodian address.') - } const symbol = 'ETH' const destinationTokenName = symbol const sourceTokenName = 'n' + symbol - const sourceToken = options.auroraEvmAccount ?? bridgeParams.auroraEvmAccount + const sourceToken = auroraEvmAccount const decimals = 18 - const withdrawReceipt = await parseWithdrawReceipt( - burnTx, - sender, - options.auroraEvmAccount ?? bridgeParams.auroraEvmAccount, - nearProvider - ) - - // @ts-expect-error TODO - const txBlock = await nearProvider.block({ blockId: burnTx.transaction_outcome.block_hash }) - // various attributes stored as arrays, to keep history of retries const transfer = { ...transferDraft, id: Math.random().toString().slice(2), - startTime: new Date(txBlock.header.timestamp / 10 ** 6).toISOString(), + startTime: new Date(withdrawReceipt.blockTimestamp / 10 ** 6).toISOString(), amount: amount.toString(), completedStep: BURN, destinationTokenName, @@ -365,76 +329,49 @@ export async function recover ( * Parse the burn receipt id and block height needed to complete * the step BURN * @param burnTx - * @param sender - * @param sourceToken + * @param auroraEvmAccount * @param nearProvider */ export async function parseWithdrawReceipt ( burnTx: FinalExecutionOutcome, - sender: string, - sourceToken: string, + auroraEvmAccount: string, nearProvider: najProviders.Provider -): Promise<{id: string, blockHeight: number }> { - const receiptIds = burnTx.transaction_outcome.outcome.receipt_ids - - if (receiptIds.length !== 1) { - throw new TransferError( - `Withdrawal expects only one receipt, got ${receiptIds.length}. - Full withdrawal transaction: ${JSON.stringify(burnTx)}` - ) +): Promise<{id: string, blockHeight: number, blockTimestamp: number, event: { amount: string, recipient: string, etherCustodian: string }}> { + // @ts-expect-error + const bridgeReceipt: any = burnTx.receipts_outcome.find(r => r.outcome.executor_id === auroraEvmAccount) + if (!bridgeReceipt) { + throw new Error(`Failed to parse bridge receipt for ${JSON.stringify(burnTx)}`) } - - // Get receipt information for recording and building burn proof - const successReceiptId = receiptIds[0] - const successReceiptOutcome = burnTx.receipts_outcome - .find(r => r.id === successReceiptId)! - .outcome - // @ts-expect-error TODO - const successReceiptExecutorId = successReceiptOutcome.executor_id - - let withdrawReceiptId: string - - // Check if this tx was made from a 2fa - switch (successReceiptExecutorId) { - case sender: { - // `confirm` transaction executed on 2fa account - // @ts-expect-error TODO - withdrawReceiptId = successReceiptOutcome.status.SuccessReceiptId - const withdrawReceiptOutcome = burnTx.receipts_outcome - .find(r => r.id === withdrawReceiptId)! - .outcome - - // @ts-expect-error TODO - const withdrawReceiptExecutorId: string = withdrawReceiptOutcome.executor_id - // Expect this receipt to be the 2fa FunctionCall - if (withdrawReceiptExecutorId !== sourceToken) { - throw new TransferError( - `Unexpected receipt outcome format in 2fa transaction. - Expected sourceToken '${sourceToken}', got '${withdrawReceiptExecutorId}' - Full withdrawal transaction: ${JSON.stringify(burnTx)}` - ) - } - break + const successValue = bridgeReceipt.outcome.status.SuccessValue + // eslint-disable-next-line @typescript-eslint/no-extraneous-class + class WithdrawEvent { + constructor (args: any) { + Object.assign(this, args) } - case sourceToken: - // `burn` called directly, successReceiptId is already correct, nothing to do - withdrawReceiptId = successReceiptId! - break - default: - throw new TransferError( - `Unexpected receipt outcome format. - Full withdrawal transaction: ${JSON.stringify(burnTx)}` - ) + } + const SCHEMA = new Map([ + [WithdrawEvent, { + kind: 'struct', + fields: [ + ['amount', 'u128'], + ['recipient_id', [20]], + ['eth_custodian_address', [20]] + ] + }] + ]) + const rawEvent = deserializeBorsh( + SCHEMA, WithdrawEvent, Buffer.from(successValue, 'base64') + ) as { amount: BN, recipient_id: Uint8Array, eth_custodian_address: Uint8Array} + const event = { + amount: rawEvent.amount.toString(), + recipient: '0x' + Buffer.from(rawEvent.recipient_id).toString('hex'), + etherCustodian: '0x' + Buffer.from(rawEvent.eth_custodian_address).toString('hex') } - const txReceiptBlockHash = burnTx.receipts_outcome - .find(r => r.id === withdrawReceiptId)! - // @ts-expect-error TODO - .block_hash - - const receiptBlock = await nearProvider.block({ blockId: txReceiptBlockHash }) - const receiptBlockHeight = Number(receiptBlock.header.height) - return { id: withdrawReceiptId, blockHeight: receiptBlockHeight } + const receiptBlock = await nearProvider.block({ blockId: bridgeReceipt.block_hash }) + const blockHeight = Number(receiptBlock.header.height) + const blockTimestamp = Number(receiptBlock.header.timestamp) + return { id: bridgeReceipt.id, blockHeight, blockTimestamp, event } } /** @@ -593,6 +530,7 @@ export async function checkBurn ( options?: { nearAccount?: Account nearProvider?: najProviders.Provider + auroraEvmAccount?: string } ): Promise { options = options ?? {} @@ -698,7 +636,8 @@ export async function checkBurn ( let withdrawReceipt try { - withdrawReceipt = await parseWithdrawReceipt(burnTx, transfer.sender, transfer.sourceToken, nearProvider) + const auroraEvmAccount = options.auroraEvmAccount ?? getBridgeParams().auroraEvmAccount + withdrawReceipt = await parseWithdrawReceipt(burnTx, auroraEvmAccount, nearProvider) } catch (e) { if (e instanceof TransferError) { if (clearParams) urlParams.clear(...clearParams) @@ -714,9 +653,7 @@ export async function checkBurn ( throw e } - // @ts-expect-error TODO - const txBlock = await nearProvider.block({ blockId: burnTx.transaction_outcome.block_hash }) - const startTime = new Date(txBlock.header.timestamp / 10 ** 6).toISOString() + const startTime = new Date(withdrawReceipt.blockTimestamp / 10 ** 6).toISOString() // Clear urlParams at the end so that if the provider connection throws, // checkStatus will be able to process it again in the next loop. diff --git a/packages/near-ether/src/natural-near/sendToEthereum/index.ts b/packages/near-ether/src/natural-near/sendToEthereum/index.ts index 48045990..c7235106 100644 --- a/packages/near-ether/src/natural-near/sendToEthereum/index.ts +++ b/packages/near-ether/src/natural-near/sendToEthereum/index.ts @@ -283,60 +283,25 @@ export async function recover ( throw new Error(`Lock transaction failed: ${lockTxHash}`) } - // Get lock event information from successValue - // @ts-expect-error TODO - const successValue: string = lockTx.status.SuccessValue - if (!successValue) { - throw new Error( - `Invalid lockTx successValue: '${successValue}' - Full lock transaction: ${JSON.stringify(lockTx)}` - ) - } - - // eslint-disable-next-line @typescript-eslint/no-extraneous-class - class LockEvent { - constructor (args: any) { - Object.assign(this, args) - } - } - const SCHEMA = new Map([ - [LockEvent, { - kind: 'struct', - fields: [ - ['flag', 'u8'], - ['amount', 'u128'], - ['recipient', [20]] - ] - }] - ]) - const lockEvent = deserializeBorsh( - SCHEMA, LockEvent, Buffer.from(successValue, 'base64') - ) as { amount: BN, recipient: Uint8Array } - - const amount = lockEvent.amount.toString() - const recipient = '0x' + Buffer.from(lockEvent.recipient).toString('hex') - const symbol = 'NEAR' - const destinationTokenName = symbol - const sourceTokenName = symbol - const sourceToken = symbol - const decimals = 24 - const lockReceipt = await parseLockReceipt( lockTx, - sender, options.nativeNEARLockerAddress ?? bridgeParams.nativeNEARLockerAddress, nearProvider ) - // @ts-expect-error TODO - const txBlock = await nearProvider.block({ blockId: lockTx.transaction_outcome.block_hash }) + const { amount, recipient } = lockReceipt.event + const symbol = 'NEAR' + const destinationTokenName = symbol + const sourceTokenName = symbol + const sourceToken = symbol + const decimals = 24 // various attributes stored as arrays, to keep history of retries const transfer = { ...transferDraft, id: Math.random().toString().slice(2), - startTime: new Date(txBlock.header.timestamp / 10 ** 6).toISOString(), + startTime: new Date(lockReceipt.blockTimestamp / 10 ** 6).toISOString(), amount, completedStep: LOCK, destinationTokenName, @@ -360,78 +325,48 @@ export async function recover ( * Parse the lock receipt id and block height needed to complete * the step LOCK * @param lockTx - * @param sender * @param nativeNEARLockerAddress * @param nearProvider */ export async function parseLockReceipt ( lockTx: FinalExecutionOutcome, - sender: string, nativeNEARLockerAddress: string, nearProvider: najProviders.Provider -): Promise<{id: string, blockHeight: number }> { - const receiptIds = lockTx.transaction_outcome.outcome.receipt_ids - - if (receiptIds.length !== 1) { - throw new TransferError( - `Lock expects only one receipt, got ${receiptIds.length}. - Full lock transaction: ${JSON.stringify(lockTx)}` - ) +): Promise<{id: string, blockHeight: number, blockTimestamp: number, event: { amount: string, recipient: string }}> { + // @ts-expect-error + const bridgeReceipt: any = lockTx.receipts_outcome.find(r => r.outcome.executor_id === nativeNEARLockerAddress) + if (!bridgeReceipt) { + throw new Error(`Failed to parse bridge receipt for ${JSON.stringify(lockTx)}`) } - - // Get receipt information for recording and building lock proof - const txReceiptId = receiptIds[0] - const successReceiptOutcome = lockTx.receipts_outcome - .find(r => r.id === txReceiptId)! - .outcome - // @ts-expect-error TODO - const successReceiptId = successReceiptOutcome.status.SuccessReceiptId - // @ts-expect-error TODO - const successReceiptExecutorId = successReceiptOutcome.executor_id - - let lockReceiptId: string - - // Check if this tx was made from a 2fa - switch (successReceiptExecutorId) { - case sender: { - // `confirm` transaction executed on 2fa account - const lockReceiptOutcome = lockTx.receipts_outcome - .find(r => r.id === successReceiptId)! - .outcome - - lockReceiptId = successReceiptId - - // @ts-expect-error TODO - const lockReceiptExecutorId: string = lockReceiptOutcome.executor_id - // Expect this receipt to be the 2fa FunctionCall - if (lockReceiptExecutorId !== nativeNEARLockerAddress) { - throw new TransferError( - `Unexpected receipt outcome format in 2fa transaction. - Expected nativeNEARLockerAddress '${nativeNEARLockerAddress}', got '${lockReceiptExecutorId}' - Full withdrawal transaction: ${JSON.stringify(lockTx)}` - ) - } - break + const successValue = bridgeReceipt.outcome.status.SuccessValue + // eslint-disable-next-line @typescript-eslint/no-extraneous-class + class LockEvent { + constructor (args: any) { + Object.assign(this, args) } - case nativeNEARLockerAddress: - // `lock` called directly, successReceiptId is already correct, nothing to do - lockReceiptId = txReceiptId! - break - default: - throw new TransferError( - `Unexpected receipt outcome format. - Full withdrawal transaction: ${JSON.stringify(lockTx)}` - ) - } - - const txReceiptBlockHash = lockTx.receipts_outcome - .find(r => r.id === lockReceiptId)! - // @ts-expect-error TODO - .block_hash - - const receiptBlock = await nearProvider.block({ blockId: txReceiptBlockHash }) - const receiptBlockHeight = Number(receiptBlock.header.height) - return { id: lockReceiptId, blockHeight: receiptBlockHeight } + } + const SCHEMA = new Map([ + [LockEvent, { + kind: 'struct', + fields: [ + ['flag', 'u8'], + ['amount', 'u128'], + ['recipient', [20]] + ] + }] + ]) + const rawEvent = deserializeBorsh( + SCHEMA, LockEvent, Buffer.from(successValue, 'base64') + ) as { amount: BN, recipient: Uint8Array } + const event = { + amount: rawEvent.amount.toString(), + recipient: '0x' + Buffer.from(rawEvent.recipient).toString('hex') + } + + const receiptBlock = await nearProvider.block({ blockId: bridgeReceipt.block_hash }) + const blockHeight = Number(receiptBlock.header.height) + const blockTimestamp = Number(receiptBlock.header.timestamp) + return { id: bridgeReceipt.id, blockHeight, blockTimestamp, event } } /** @@ -683,7 +618,6 @@ export async function checkLock ( try { lockReceipt = await parseLockReceipt( lockTx, - transfer.sender, options.nativeNEARLockerAddress ?? bridgeParams.nativeNEARLockerAddress, nearProvider ) @@ -702,9 +636,7 @@ export async function checkLock ( throw e } - // @ts-expect-error TODO - const txBlock = await nearProvider.block({ blockId: lockTx.transaction_outcome.block_hash }) - const startTime = new Date(txBlock.header.timestamp / 10 ** 6).toISOString() + const startTime = new Date(lockReceipt.blockTimestamp / 10 ** 6).toISOString() // Clear urlParams at the end so that if the provider connection throws, // checkStatus will be able to process it again in the next loop. diff --git a/packages/nep141-erc20/src/bridged-nep141/sendToEthereum/index.ts b/packages/nep141-erc20/src/bridged-nep141/sendToEthereum/index.ts index efe10e95..c700c3cc 100644 --- a/packages/nep141-erc20/src/bridged-nep141/sendToEthereum/index.ts +++ b/packages/nep141-erc20/src/bridged-nep141/sendToEthereum/index.ts @@ -296,57 +296,22 @@ export async function recover ( throw new Error(`Withdraw transaction failed: ${withdrawTxHash}`) } - // Get withdraw event information from successValue - // @ts-expect-error TODO - const successValue: string = withdrawTx.status.SuccessValue - if (!successValue) { - throw new Error( - `Invalid withdrawTx successValue: '${successValue}' - Full withdrawal transaction: ${JSON.stringify(withdrawTx)}` - ) - } - - // eslint-disable-next-line @typescript-eslint/no-extraneous-class - class WithdrawEvent { - constructor (args: any) { - Object.assign(this, args) - } - } - const SCHEMA = new Map([ - [WithdrawEvent, { - kind: 'struct', - fields: [ - ['flag', 'u8'], - ['amount', 'u128'], - ['token', [20]], - ['recipient', [20]] - ] - }] - ]) - const withdrawEvent = deserializeBorsh( - SCHEMA, WithdrawEvent, Buffer.from(successValue, 'base64') - ) as { amount: BN, token: Uint8Array, recipient: Uint8Array} + const nep141Factory = options.nep141Factory ?? getBridgeParams().nep141Factory + const withdrawReceipt = await parseWithdrawReceipt(withdrawTx, nep141Factory, nearProvider) - const amount = withdrawEvent.amount.toString() - const recipient = '0x' + Buffer.from(withdrawEvent.recipient).toString('hex') - const erc20Address = '0x' + Buffer.from(withdrawEvent.token).toString('hex') + const { amount, recipient, token: erc20Address } = withdrawReceipt.event const symbol = options.symbol ?? await getSymbol({ erc20Address, options }) const destinationTokenName = symbol const sourceTokenName = 'n' + symbol const sourceToken = getNep141Address({ erc20Address, options }) const decimals = options.decimals ?? await getDecimals({ erc20Address, options }) - const withdrawReceipt = await parseWithdrawReceipt(withdrawTx, sender, sourceToken, nearProvider) - - // @ts-expect-error TODO - const txBlock = await nearProvider.block({ blockId: withdrawTx.transaction_outcome.block_hash }) - // various attributes stored as arrays, to keep history of retries const transfer = { ...transferDraft, id: Math.random().toString().slice(2), - startTime: new Date(txBlock.header.timestamp / 10 ** 6).toISOString(), + startTime: new Date(withdrawReceipt.blockTimestamp / 10 ** 6).toISOString(), amount, completedStep: WITHDRAW, destinationTokenName, @@ -370,80 +335,50 @@ export async function recover ( * Parse the withdraw receipt id and block height needed to complete * the step WITHDRAW * @param withdrawTx - * @param sender - * @param sourceToken + * @param nep141Factory * @param nearProvider */ export async function parseWithdrawReceipt ( withdrawTx: FinalExecutionOutcome, - sender: string, - sourceToken: string, + nep141Factory: string, nearProvider: najProviders.Provider -): Promise<{id: string, blockHeight: number }> { - const receiptIds = withdrawTx.transaction_outcome.outcome.receipt_ids - - if (receiptIds.length !== 1) { - throw new TransferError( - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `Withdrawal expects only one receipt, got ${receiptIds.length}. - Full withdrawal transaction: ${JSON.stringify(withdrawTx)}` - ) +): Promise<{id: string, blockHeight: number, blockTimestamp: number, event: { amount: string, token: string, recipient: string }}> { + // @ts-expect-error + const bridgeReceipt: any = withdrawTx.receipts_outcome.find(r => r.outcome.executor_id === nep141Factory) + if (!bridgeReceipt) { + throw new Error(`Failed to parse bridge receipt for ${JSON.stringify(withdrawTx)}`) } - - // Get receipt information for recording and building withdraw proof - const txReceiptId = receiptIds[0] - const successReceiptOutcome = withdrawTx.receipts_outcome - .find(r => r.id === txReceiptId)! - .outcome - // @ts-expect-error TODO - const successReceiptId = successReceiptOutcome.status.SuccessReceiptId - // @ts-expect-error TODO - const successReceiptExecutorId = successReceiptOutcome.executor_id - - let withdrawReceiptId: string - - // Check if this tx was made from a 2fa - switch (successReceiptExecutorId) { - case sender: { - // `confirm` transaction executed on 2fa account - const withdrawReceiptOutcome = withdrawTx.receipts_outcome - .find(r => r.id === successReceiptId)! - .outcome - - // @ts-expect-error TODO - withdrawReceiptId = withdrawReceiptOutcome.status.SuccessReceiptId - - // @ts-expect-error TODO - const withdrawReceiptExecutorId: string = withdrawReceiptOutcome.executor_id - // Expect this receipt to be the 2fa FunctionCall - if (withdrawReceiptExecutorId !== sourceToken) { - throw new TransferError( - `Unexpected receipt outcome format in 2fa transaction. - Expected sourceToken '${sourceToken}', got '${withdrawReceiptExecutorId}' - Full withdrawal transaction: ${JSON.stringify(withdrawTx)}` - ) - } - break + const successValue = bridgeReceipt.outcome.status.SuccessValue + // eslint-disable-next-line @typescript-eslint/no-extraneous-class + class WithdrawEvent { + constructor (args: any) { + Object.assign(this, args) } - case sourceToken: - // `withdraw` called directly, successReceiptId is already correct, nothing to do - withdrawReceiptId = successReceiptId - break - default: - throw new TransferError( - `Unexpected receipt outcome format. - Full withdrawal transaction: ${JSON.stringify(withdrawTx)}` - ) + } + const SCHEMA = new Map([ + [WithdrawEvent, { + kind: 'struct', + fields: [ + ['flag', 'u8'], + ['amount', 'u128'], + ['token', [20]], + ['recipient', [20]] + ] + }] + ]) + const rawEvent = deserializeBorsh( + SCHEMA, WithdrawEvent, Buffer.from(successValue, 'base64') + ) as { amount: BN, token: Uint8Array, recipient: Uint8Array} + const event = { + amount: rawEvent.amount.toString(), + token: '0x' + Buffer.from(rawEvent.token).toString('hex'), + recipient: '0x' + Buffer.from(rawEvent.recipient).toString('hex') } - const txReceiptBlockHash = withdrawTx.receipts_outcome - .find(r => r.id === withdrawReceiptId)! - // @ts-expect-error TODO - .block_hash - - const receiptBlock = await nearProvider.block({ blockId: txReceiptBlockHash }) - const receiptBlockHeight = Number(receiptBlock.header.height) - return { id: withdrawReceiptId, blockHeight: receiptBlockHeight } + const receiptBlock = await nearProvider.block({ blockId: bridgeReceipt.block_hash }) + const blockHeight = Number(receiptBlock.header.height) + const blockTimestamp = Number(receiptBlock.header.timestamp) + return { id: bridgeReceipt.id, blockHeight, blockTimestamp, event } } /** @@ -596,6 +531,7 @@ export async function checkWithdraw ( options?: { nearAccount?: Account nearProvider?: najProviders.Provider + nep141Factory?: string } ): Promise { options = options ?? {} @@ -700,8 +636,9 @@ export async function checkWithdraw ( } let withdrawReceipt + const nep141Factory = options.nep141Factory ?? getBridgeParams().nep141Factory try { - withdrawReceipt = await parseWithdrawReceipt(withdrawTx, transfer.sender, transfer.sourceToken, nearProvider) + withdrawReceipt = await parseWithdrawReceipt(withdrawTx, nep141Factory, nearProvider) } catch (e) { if (e instanceof TransferError) { if (clearParams) urlParams.clear(...clearParams) @@ -717,9 +654,7 @@ export async function checkWithdraw ( throw e } - // @ts-expect-error TODO - const txBlock = await nearProvider.block({ blockId: withdrawTx.transaction_outcome.block_hash }) - const startTime = new Date(txBlock.header.timestamp / 10 ** 6).toISOString() + const startTime = new Date(withdrawReceipt.blockTimestamp / 10 ** 6).toISOString() // Clear urlParams at the end so that if the provider connection throws, // checkStatus will be able to process it again in the next loop.