From cbd3876074622833aa99fd323e71d75872482f83 Mon Sep 17 00:00:00 2001 From: Albert Folch Date: Wed, 6 Aug 2025 10:15:30 +0200 Subject: [PATCH 1/2] feat: add getCurrentTimeMs in web3libadapter for assertCompletableExchange --- packages/common/src/types/web3-lib-adapter.ts | 6 ++-- packages/common/tests/mocks.ts | 15 +++------ packages/core-sdk/src/exchanges/handler.ts | 26 +++++++++++----- .../src/eth-connect-adapter.ts | 13 +++++--- packages/eth-connect-sdk/tests/index.test.ts | 21 ++++++++++--- packages/ethers-sdk/src/ethers-adapter.ts | 5 +++ packages/ethers-sdk/tests/index.test.ts | 21 ++++++++----- .../src/lib/signer/externalSigner.ts | 31 ++++--------------- 8 files changed, 76 insertions(+), 62 deletions(-) diff --git a/packages/common/src/types/web3-lib-adapter.ts b/packages/common/src/types/web3-lib-adapter.ts index 0995f714e..dc8409725 100644 --- a/packages/common/src/types/web3-lib-adapter.ts +++ b/packages/common/src/types/web3-lib-adapter.ts @@ -1,9 +1,6 @@ import { BigNumberish } from "@ethersproject/bignumber"; -export type Log = { - data: string; - topics: string[]; -}; +export type Log = { data: string; topics: string[] }; export type TransactionRequest = Partial<{ to: string; @@ -48,4 +45,5 @@ export interface Web3LibAdapter { call(transactionRequest: TransactionRequest): Promise; send(rpcMethod: string, payload: unknown[]): Promise; getTransactionReceipt(txHash: string): Promise; + getCurrentTimeMs(): Promise; } diff --git a/packages/common/tests/mocks.ts b/packages/common/tests/mocks.ts index 13641361f..367db8a81 100644 --- a/packages/common/tests/mocks.ts +++ b/packages/common/tests/mocks.ts @@ -36,12 +36,7 @@ export function mockOfferStruct(overrides?: Partial): OfferStruct { metadataUri: IPFS_URI, metadataHash: IPFS_HASH, priceType: PriceType.Static, - royaltyInfo: [ - { - recipients: [AddressZero], - bps: [0] - } - ], + royaltyInfo: [{ recipients: [AddressZero], bps: [0] }], ...overrides }; } @@ -158,10 +153,10 @@ export class MockWeb3LibAdapter implements Web3LibAdapter { constructor(returnValues: Partial = {}) { this.uuid = crypto.randomUUID(); - this._returnValues = { - ...defaultMockedReturnValues, - ...returnValues - }; + this._returnValues = { ...defaultMockedReturnValues, ...returnValues }; + } + getCurrentTimeMs(): Promise { + return Promise.resolve(Date.now()); } async getTransactionReceipt(txHash: string): Promise { this.getTransactionReceiptArgs.push(txHash); diff --git a/packages/core-sdk/src/exchanges/handler.ts b/packages/core-sdk/src/exchanges/handler.ts index 9dfc42eb4..364b11e86 100644 --- a/packages/core-sdk/src/exchanges/handler.ts +++ b/packages/core-sdk/src/exchanges/handler.ts @@ -218,7 +218,12 @@ export async function completeExchange( args.web3Lib.getSignerAddress() ]); - assertCompletableExchange(args.exchangeId, exchange, signerAddress); + await assertCompletableExchange( + args.exchangeId, + exchange, + signerAddress, + args.web3Lib + ); const transactionRequest = { to: args.contractAddress, @@ -257,15 +262,18 @@ export async function completeExchangeBatch( ): Promise { const [exchanges, signerAddress] = await Promise.all([ getExchanges(args.subgraphUrl, { - exchangesFilter: { - id_in: args.exchangeIds.map((id) => id.toString()) - } + exchangesFilter: { id_in: args.exchangeIds.map((id) => id.toString()) } }), args.web3Lib.getSignerAddress() ]); for (const exchange of exchanges) { - assertCompletableExchange(exchange.id, exchange, signerAddress); + assertCompletableExchange( + exchange.id, + exchange, + signerAddress, + args.web3Lib + ); } const transactionRequest = { @@ -522,10 +530,11 @@ function assertSignerIsBuyerOrAssistant( return { isSignerBuyer, isSignerAssistant }; } -function assertCompletableExchange( +async function assertCompletableExchange( exchangeId: BigNumberish, exchange: ExchangeFieldsFragment | null, - signer: string + signer: string, + web3Lib: Web3LibAdapter ) { assertExchange(exchangeId, exchange); @@ -535,8 +544,9 @@ function assertCompletableExchange( ); if (isSignerAssistant && !isSignerBuyer) { + const now = await web3Lib.getCurrentTimeMs(); const elapsedSinceRedeemMS = - Date.now() - Number(exchange.redeemedDate || "0") * 1000; + now - Number(exchange.redeemedDate || "0") * 1000; const didDisputePeriodElapse = elapsedSinceRedeemMS > Number(exchange.offer.disputePeriodDuration) * 1000; diff --git a/packages/eth-connect-sdk/src/eth-connect-adapter.ts b/packages/eth-connect-sdk/src/eth-connect-adapter.ts index 2294531a6..6be55b66a 100644 --- a/packages/eth-connect-sdk/src/eth-connect-adapter.ts +++ b/packages/eth-connect-sdk/src/eth-connect-adapter.ts @@ -36,6 +36,14 @@ export class EthConnectAdapter implements Web3LibAdapter { this._externalFeatures = externalFeatures; } + async getCurrentTimeMs(): Promise { + const { timestamp } = await this._requestManager.eth_getBlockByNumber( + "latest", + false + ); + return Number(timestamp.valueOf()) * 1000; // Convert seconds to milliseconds + } + public async getSignerAddress() { if (this._externalFeatures?.getSignerAddress) { const address = await this._externalFeatures?.getSignerAddress(); @@ -97,10 +105,7 @@ export class EthConnectAdapter implements Web3LibAdapter { // Use standard requestManager to fetch blockchain information const blockNumber = await this._requestManager.eth_blockNumber(); return this._requestManager.eth_call( - { - data: transactionRequest.data, - to: transactionRequest.to - }, + { data: transactionRequest.data, to: transactionRequest.to }, blockNumber ); } diff --git a/packages/eth-connect-sdk/tests/index.test.ts b/packages/eth-connect-sdk/tests/index.test.ts index b1b5cb84b..11d994d77 100644 --- a/packages/eth-connect-sdk/tests/index.test.ts +++ b/packages/eth-connect-sdk/tests/index.test.ts @@ -14,6 +14,7 @@ const CALL_RET = "call_ret"; const TRANSACTION_COUNT = 64; const GAS_USED = "87654"; const TX_HASH = "transactionHash"; +const nowInSec = Math.floor(Date.now() / 1000); test("imports EthConnectAdapter", () => { expect(EthConnectAdapter).toBeTruthy(); @@ -161,6 +162,17 @@ test("EthConnectAdapter getTransactionReceipt", async () => { expect(txReceipt.transactionHash).toEqual(TX_HASH); }); +test("EthConnectAdapter getCurrentTimeMs", async () => { + const requestManager = mockRequestManager(); + const externalFeatures = mockExternalFeatures(); + const ethConnectAdapter = new EthConnectAdapter( + requestManager, + externalFeatures + ); + const nowMs = await ethConnectAdapter.getCurrentTimeMs(); + expect(nowMs).toBe(nowInSec * 1000); // Convert seconds to milliseconds +}); + function mockSigner(wallet: string): RequestManager { return mockRequestManager(wallet); } @@ -187,7 +199,10 @@ function mockRequestManager(wallet?: string): RequestManager { }, eth_accounts: async () => (wallet ? [wallet] : WALLETS), eth_sendTransaction: async (t: any) => TX_HASH, - sendAsync: async (t: any) => TX_HASH + sendAsync: async (t: any) => TX_HASH, + eth_getBlockByNumber: async (blockNumber: string, fullTx: boolean) => { + return { timestamp: nowInSec }; + } } as unknown as RequestManager; } @@ -200,7 +215,5 @@ function mockExternalFeatures(signerAddress?: string): ExternalFeatures { } } as ExternalFeatures; } - return { - delay: async (ms: number) => undefined - } as ExternalFeatures; + return { delay: async (ms: number) => undefined } as ExternalFeatures; } diff --git a/packages/ethers-sdk/src/ethers-adapter.ts b/packages/ethers-sdk/src/ethers-adapter.ts index d9e5b7894..709226faa 100644 --- a/packages/ethers-sdk/src/ethers-adapter.ts +++ b/packages/ethers-sdk/src/ethers-adapter.ts @@ -40,6 +40,11 @@ export class EthersAdapter implements Web3LibAdapter { : this._provider.getSigner(); } + async getCurrentTimeMs(): Promise { + const { timestamp } = await this._provider.getBlock("latest"); + return timestamp * 1000; // Convert seconds to milliseconds + } + public async getSignerAddress() { return this._signer.getAddress(); } diff --git a/packages/ethers-sdk/tests/index.test.ts b/packages/ethers-sdk/tests/index.test.ts index cbfa22e7d..db5388e5c 100644 --- a/packages/ethers-sdk/tests/index.test.ts +++ b/packages/ethers-sdk/tests/index.test.ts @@ -12,6 +12,7 @@ const BLOCK_NUMBER = "42"; const CALL_RET = "call_ret"; const GAS_USED = "87654"; const TX_HASH = "transactionHash"; +const nowInSec = Math.floor(Date.now() / 1000); test("imports EthersAdapter", () => { expect(EthersAdapter).toBeTruthy(); @@ -24,7 +25,7 @@ test("EthersAdapter constructor", () => { expect(ethersAdapter).toBeTruthy(); }); -test("EthConnectAdapter getSignerAddress without signer", async () => { +test("EthersAdapter getSignerAddress without signer", async () => { const provider = mockProvider(); const ethersAdapter = new EthersAdapter(provider); expect(ethersAdapter).toBeTruthy(); @@ -32,7 +33,7 @@ test("EthConnectAdapter getSignerAddress without signer", async () => { expect(signerAddress).toEqual(WALLETS[0]); }); -test("EthConnectAdapter getSignerAddress with signer", async () => { +test("EthersAdapter getSignerAddress with signer", async () => { const provider = mockProvider(); const signer = mockSigner(WALLETS[2]); const ethersAdapter = new EthersAdapter(provider, signer); @@ -41,6 +42,14 @@ test("EthConnectAdapter getSignerAddress with signer", async () => { expect(signerAddress).toEqual(WALLETS[2]); }); +test("EthersAdapter getCurrentTimeMs", async () => { + const provider = mockProvider(); + const ethersAdapter = new EthersAdapter(provider); + expect(ethersAdapter).toBeTruthy(); + const nowMs = await ethersAdapter.getCurrentTimeMs(); + expect(nowMs).toBe(nowInSec * 1000); // Convert seconds to milliseconds +}); + function mockProvider(): Provider { return { getBalance: async () => { @@ -58,7 +67,8 @@ function mockProvider(): Provider { }, send: async () => TX_HASH, getSigner: () => mockSigner(WALLETS[0]), - getCode: async () => "0x" + getCode: async () => "0x", + getBlock: async () => ({ timestamp: nowInSec }) } as unknown as Provider; } @@ -67,8 +77,5 @@ function mockSigner(wallet: string): Signer { getAddress: async () => wallet, getChainId: async () => CHAIN_ID }; - return { - ...signer, - connect: () => signer - } as unknown as Signer; + return { ...signer, connect: () => signer } as unknown as Signer; } diff --git a/packages/react-kit/src/lib/signer/externalSigner.ts b/packages/react-kit/src/lib/signer/externalSigner.ts index 23635b2e5..295dc5f76 100644 --- a/packages/react-kit/src/lib/signer/externalSigner.ts +++ b/packages/react-kit/src/lib/signer/externalSigner.ts @@ -38,13 +38,7 @@ const getDefaultHandleSignerFunction = ({ } } window.addEventListener("message", onMessageReceived); - window.parent.postMessage( - { - function: functionName, - args - }, - parentOrigin - ); + window.parent.postMessage({ function: functionName, args }, parentOrigin); }); }; @@ -55,6 +49,7 @@ const getExternalWeb3LibAdapterListener = ({ }): Web3LibAdapter => { return { uuid: crypto.randomUUID(), + getCurrentTimeMs: () => Promise.resolve(Date.now()), getSignerAddress: (): Promise => { return getDefaultHandleSignerFunction({ parentOrigin, @@ -145,10 +140,7 @@ const getExternalWeb3LibAdapterListener = ({ } window.addEventListener("message", onMessageReceived); window.parent.postMessage( - { - function: functionName, - args: [transactionRequest] - }, + { function: functionName, args: [transactionRequest] }, parentOrigin ); }); @@ -289,10 +281,7 @@ const getExternalSignerListener = ({ } window.addEventListener("message", onMessageReceived); window.parent.postMessage( - { - function: functionName, - args: [transactionRequest] - }, + { function: functionName, args: [transactionRequest] }, parentOrigin ); }); @@ -330,11 +319,7 @@ const getExternalSignerListener = ({ ): ReturnType => { return getDefaultHandleSignerFunction< ReturnType - >({ - parentOrigin, - functionName: "populateTransaction", - args - }); + >({ parentOrigin, functionName: "populateTransaction", args }); }, estimateGas: async (...args: any[]): ReturnType => { return getDefaultHandleSignerFunction>({ @@ -353,11 +338,7 @@ const getExternalSignerListener = ({ _checkProvider: async (...args: any[]): Promise => { return getDefaultHandleSignerFunction< ReturnType - >({ - parentOrigin, - functionName: "_checkProvider", - args - }); + >({ parentOrigin, functionName: "_checkProvider", args }); }, connect: (..._args: any[]): ReturnType => { // TODO: how can we implement this? From fe67046772f1fceec68521851dd8adb550e03da1 Mon Sep 17 00:00:00 2001 From: albertfolch-redeemeum <102516373+albertfolch-redeemeum@users.noreply.github.com> Date: Wed, 6 Aug 2025 10:26:29 +0200 Subject: [PATCH 2/2] Update packages/core-sdk/src/exchanges/handler.ts Co-authored-by: Ludovic Levalleux --- packages/core-sdk/src/exchanges/handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-sdk/src/exchanges/handler.ts b/packages/core-sdk/src/exchanges/handler.ts index 364b11e86..29dc4e680 100644 --- a/packages/core-sdk/src/exchanges/handler.ts +++ b/packages/core-sdk/src/exchanges/handler.ts @@ -268,7 +268,7 @@ export async function completeExchangeBatch( ]); for (const exchange of exchanges) { - assertCompletableExchange( + await assertCompletableExchange( exchange.id, exchange, signerAddress,