From b6f07adb6654eb6f996d8524859c863eb417ccac Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 3 Apr 2025 10:55:01 +0300 Subject: [PATCH] feat: implement minimum stand --- .cweb-config/dapp-ecosystem-devnet.yaml | 13 +- .cweb-config/dapp-ecosystem-prod.yaml | 7 +- .cweb-config/dapp-ecosystem-test.yaml | 6 +- eslint.config.mjs | 2 + packages/contract.cm/package.json | 5 +- packages/contract.cm/src/offchain/api.ts | 130 ++++-------------- packages/contract.cm/src/offchain/index.ts | 3 - .../src/offchain/shared/constants.ts | 92 +------------ .../contract.cm/src/offchain/shared/index.ts | 2 - .../contract.cm/src/offchain/shared/keys.ts | 112 ++------------- .../contract.cm/src/offchain/shared/types.ts | 125 +---------------- .../contract.cm/src/offchain/shared/utils.ts | 101 -------------- packages/contract.cm/src/offchain/types.ts | 52 ------- .../contract.cm/src/offchain/uiCommands.ts | 130 +----------------- packages/contract.cm/src/onchain/addWord.ts | 21 +++ packages/contract.cm/src/onchain/constants.ts | 8 -- packages/contract.cm/src/onchain/contract.ts | 56 ++------ packages/contract.cm/src/onchain/types.ts | 64 --------- packages/cwait/package.json | 4 +- packages/cwait/src/contract-kit/index.ts | 3 + packages/cwait/src/contract-kit/methods.ts | 59 ++++++++ .../cwait/src/contract-kit/quickjs/os.d.ts | 3 + .../cwait/src/contract-kit/quickjs/std.d.ts | 3 + packages/cwait/src/contract-kit/store.d.ts | 11 ++ packages/cwait/src/contract-kit/types.ts | 7 + packages/cwait/src/contract-kit/wrappers.ts | 17 +++ packages/cwait/src/index.ts | 3 + packages/cwait/src/onchain/context/context.ts | 14 +- packages/cwait/src/onchain/executor.ts | 84 +++++++++++ packages/cwait/src/onchain/index.ts | 3 + packages/cwait/src/onchain/ops/read.ts | 6 +- packages/cwait/src/onchain/ops/store.ts | 8 +- packages/cwait/src/onchain/ops/take.ts | 6 +- packages/cwait/src/onchain/signal.ts | 1 - .../features/uiCommands/constructCall.ts | 48 +++++-- .../lib/src/onchain/contract/createHandler.ts | 2 +- packages/lib/src/onchain/features/logger.ts | 2 +- packages/lib/src/onchain/features/queue.ts | 2 +- packages/ui/.env | 2 + packages/ui/package.json | 1 + packages/ui/src/App.tsx | 51 ++++++- scripts/update-ui-env.js | 57 ++------ yarn.lock | 10 ++ 43 files changed, 448 insertions(+), 888 deletions(-) delete mode 100644 packages/contract.cm/src/offchain/shared/utils.ts delete mode 100644 packages/contract.cm/src/offchain/types.ts create mode 100644 packages/contract.cm/src/onchain/addWord.ts delete mode 100644 packages/contract.cm/src/onchain/constants.ts delete mode 100644 packages/contract.cm/src/onchain/types.ts create mode 100644 packages/cwait/src/contract-kit/index.ts create mode 100644 packages/cwait/src/contract-kit/methods.ts create mode 100644 packages/cwait/src/contract-kit/quickjs/os.d.ts create mode 100644 packages/cwait/src/contract-kit/quickjs/std.d.ts create mode 100644 packages/cwait/src/contract-kit/store.d.ts create mode 100644 packages/cwait/src/contract-kit/types.ts create mode 100644 packages/cwait/src/contract-kit/wrappers.ts create mode 100644 packages/cwait/src/index.ts create mode 100644 packages/cwait/src/onchain/executor.ts delete mode 100644 packages/cwait/src/onchain/signal.ts diff --git a/.cweb-config/dapp-ecosystem-devnet.yaml b/.cweb-config/dapp-ecosystem-devnet.yaml index ef90758..eeb467c 100644 --- a/.cweb-config/dapp-ecosystem-devnet.yaml +++ b/.cweb-config/dapp-ecosystem-devnet.yaml @@ -10,8 +10,15 @@ includes: - url: >- - https://gitlab.com/coinweb/cweb-dapp-index/-/raw/b534ebc0dbe7faa3458f11e2bc19553ab70bc317/dapp_lock_index.yaml - blake3: 48c6cd4b1b9252f2513efca47a0a8bd2f61417c6adc6bcd9d7e30521e735545e + https://gitlab.com/coinweb/cweb-dapp-index/-/raw/e7180f56cbcf7c04fbdee487f271500cff637067/dapp_lock_index.yaml + blake3: 321b1f88930aead7fe47961c8664005b3441f6502824769bf21eb06c3e5aaba4 + +use: + - jump-listener.cm v0.1.8 + - jump_listener_devnet v0.1.8 + - jump-forwarder.cm v0.1.5 + - jump_forwarder_devnet v0.1.5 + interpreters: {} @@ -24,4 +31,6 @@ contract_templates: contract_instances: - alias: cwait-contract 0.0.1-devnet template: contract.cm v0.0.1 + parameters: + content: [] diff --git a/.cweb-config/dapp-ecosystem-prod.yaml b/.cweb-config/dapp-ecosystem-prod.yaml index 5947999..cea6693 100644 --- a/.cweb-config/dapp-ecosystem-prod.yaml +++ b/.cweb-config/dapp-ecosystem-prod.yaml @@ -10,8 +10,8 @@ includes: - url: >- - https://gitlab.com/coinweb/cweb-dapp-index/-/raw/35d7542b81d884adb899ea95f1433fe59b012a6c/dapp_lock_index.yaml - blake3: 0e8f57c2c939f9cccf6912c2fb7994bb7095a1f10bef585296dba4263eb76211 + https://gitlab.com/coinweb/cweb-dapp-index/-/raw/e7180f56cbcf7c04fbdee487f271500cff637067/dapp_lock_index.yaml + blake3: 321b1f88930aead7fe47961c8664005b3441f6502824769bf21eb06c3e5aaba4 interpreters: {} @@ -23,4 +23,5 @@ contract_templates: contract_instances: - alias: cwait-contract 0.0.1-prod template: contract.cm v0.0.1 - + parameters: + content: [] diff --git a/.cweb-config/dapp-ecosystem-test.yaml b/.cweb-config/dapp-ecosystem-test.yaml index 531f84a..779766e 100644 --- a/.cweb-config/dapp-ecosystem-test.yaml +++ b/.cweb-config/dapp-ecosystem-test.yaml @@ -10,8 +10,8 @@ includes: - url: >- - https://gitlab.com/coinweb/cweb-dapp-index/-/raw/5d52c2c4c39fbe74b7e86e9a9c463240df69b01d/dapp_lock_index.yaml - blake3: a576de68122e704240acc7efe813d52cecd1519fab1078db537ed4b59da0f9ba + https://gitlab.com/coinweb/cweb-dapp-index/-/raw/e7180f56cbcf7c04fbdee487f271500cff637067/dapp_lock_index.yaml + blake3: 321b1f88930aead7fe47961c8664005b3441f6502824769bf21eb06c3e5aaba4 use: - dex-app.cm v0.0.67+test @@ -29,3 +29,5 @@ contract_templates: contract_instances: - alias: cwait-contract 0.0.1-test template: contract.cm v0.0.1 + parameters: + content: [] diff --git a/eslint.config.mjs b/eslint.config.mjs index 22efe8d..2930d75 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -121,6 +121,8 @@ export default tseslint.config( allowTypedFunctionExpressions: true, }, ], + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/require-await': 'off', }, }, diff --git a/packages/contract.cm/package.json b/packages/contract.cm/package.json index 3d86a01..8cfdfc4 100644 --- a/packages/contract.cm/package.json +++ b/packages/contract.cm/package.json @@ -26,5 +26,8 @@ "bech32": "2.0.0", "bs58": "6.0.0" }, - "main": "cweb_dist/offchain/index.js" + "main": "cweb_dist/offchain/index.js", + "engines": { + "cweb_interpreter": "a344c6003922f9e44385f6e8234a7d2567d9a676b14330ad3b42cbd1948a92bf" + } } diff --git a/packages/contract.cm/src/offchain/api.ts b/packages/contract.cm/src/offchain/api.ts index 161f892..3511ade 100644 --- a/packages/contract.cm/src/offchain/api.ts +++ b/packages/contract.cm/src/offchain/api.ts @@ -1,115 +1,41 @@ -import { ClaimKey, User } from '@coinweb/contract-kit'; -import { Client } from 'cwap-cm-lib/offchain'; +import { ClaimKey } from '@coinweb/contract-kit'; +import { Client } from 'lib/offchain'; -import { - createActiveOrderIndexFirstPart, - createBestByQuoteActiveIndexFirstPart, - createBestByQuoteIndexFirstPart, - createDateIndexFirstPart, - createL1TxInfoFirstPart, - createOrderStateKey, - createOwnerlessIndexFirstPart, - createUniquenessFirstPart, - createUserIndexFirstPart, -} from './shared/keys'; -import { BtcChainData, L1TxInfoData, OrderStateClaimBody } from './shared/types'; -import { OrderData } from './types'; +import { SetClaimBody, WordClaimBody } from './shared'; +import { createWordFirstPart, createSetKey, createWordKey, createByLetterFirstPart } from './shared/keys'; -export const getActiveOrderIds = async (client: Client): Promise => { - const claimsResponse = await client.fetchClaims(createActiveOrderIndexFirstPart(), null); - - return claimsResponse.map(({ content }) => ((content.key as ClaimKey).second_part as [number, string])[1]); -}; - -export const getBestOrderIds = async (client: Client): Promise => { - const claimsResponse = await client.fetchClaims(createBestByQuoteIndexFirstPart(), null); - - return claimsResponse.map(({ content }) => ((content.key as ClaimKey).second_part as [number, string])[1]); -}; - -export const getBestActiveOrderIds = async (client: Client): Promise => { - const claimsResponse = await client.fetchClaims(createBestByQuoteActiveIndexFirstPart(), null); - - return claimsResponse.map(({ content }) => ((content.key as ClaimKey).second_part as [number, string])[1]); -}; - -export const getLastOrderIds = async (client: Client): Promise => { - const claimsResponse = await client.fetchClaims(createDateIndexFirstPart(), null); - - return claimsResponse.map(({ content }) => ((content.key as ClaimKey).second_part as [number, string])[1]); -}; - -export const getUserOrderIds = async (client: Client, user: User): Promise => { - const claimsResponse = await client.fetchClaims(createUserIndexFirstPart(user), null); - - return claimsResponse.map(({ content }) => ((content.key as ClaimKey).second_part as [number, string])[1]); -}; - -export const getOwnerlessOrderIds = async (client: Client): Promise => { - const claimsResponse = await client.fetchClaims(createOwnerlessIndexFirstPart(), null); - - return claimsResponse.map(({ content }) => ((content.key as ClaimKey).second_part as [string])[0]); -}; - -export const getUtxosInUse = async (client: Client): Promise[]> => { - const claimsResponse = await client.fetchClaims(createUniquenessFirstPart(), null); - - return claimsResponse.map(({ content: { key } }) => { - const [l1TxId, vout] = (key as ClaimKey).second_part as [BtcChainData['l1TxId'], BtcChainData['vout']]; - - return { - l1TxId, - vout, - }; - }); -}; - -export const getOrderById = async (client: Client, id: string) => { - const key = createOrderStateKey(id); +export const getWord = async (client: Client, id: string) => { + const key = createWordKey(id); const claimResponse = (await client.fetchClaims(key.first_part, key.second_part))[0]; - if (!claimResponse) { - throw new Error('Order not found'); - } + const data = claimResponse.content.body as WordClaimBody; - const data = claimResponse.content.body as OrderStateClaimBody; - - return { - id, - baseAmount: BigInt(data.baseAmount), - l1Amount: BigInt(data.l1Amount), - minL1Amount: BigInt(data.minL1Amount), - recipient: data.recipient, - createdAt: data.createdAt, - activityStatus: data.activityStatus, - paymentStatus: data.paymentStatus, - funds: BigInt(data.funds), - chainData: data.chainData, - txId: data.txId, - error: data.error, - expirationDate: data.expirationDate, - history: data.history, - isOwnerless: data.isOwnerless, - } satisfies OrderData; + return data.word; }; -export const getOwnerlessOrders = async (client: Client) => { - const ids = await getOwnerlessOrderIds(client); +export const getWords = async (client: Client): Promise => { + const claimsResponse = await client.fetchClaims(createWordFirstPart(), null); - try { - const orders = await Promise.all(ids.map((id) => getOrderById(client, id))); - - return orders; - } catch (e) { - console.error({ e }); - - return []; - } + return claimsResponse.map(({ content }) => (content.body as WordClaimBody).word); }; -export const getL1TxInfo = async (client: Client, user: User): Promise => { - const claimsResponse = await client.fetchClaims(createL1TxInfoFirstPart(user), null); +export const getWordsByLetter = async (client: Client, letter: string): Promise => { + const indexResponse = await client.fetchClaims(createByLetterFirstPart(letter), null); - return claimsResponse.map((claim) => claim.content.body as L1TxInfoData); + const ids = indexResponse.map(({ content }) => ((content.key as ClaimKey).second_part as [string])[0]); + + const words = await Promise.all(ids.map((id) => getWord(client, id))); + + return words; +}; + +export const getSet = async (client: Client, letter: string): Promise => { + const key = createSetKey(letter); + + const claimResponse = (await client.fetchClaims(key.first_part, key.second_part))[0]; + + const data = claimResponse.content.body as SetClaimBody; + + return data.set; }; diff --git a/packages/contract.cm/src/offchain/index.ts b/packages/contract.cm/src/offchain/index.ts index 10aa50a..8a9677c 100644 --- a/packages/contract.cm/src/offchain/index.ts +++ b/packages/contract.cm/src/offchain/index.ts @@ -1,6 +1,3 @@ export * from './api'; export * from './uiCommands'; -export * from './types'; export * from './shared'; -export * from './createL1TxData'; -export * from './reexport'; diff --git a/packages/contract.cm/src/offchain/shared/constants.ts b/packages/contract.cm/src/offchain/shared/constants.ts index cd6c4d0..d687481 100644 --- a/packages/contract.cm/src/offchain/shared/constants.ts +++ b/packages/contract.cm/src/offchain/shared/constants.ts @@ -1,95 +1,13 @@ -import { BtcNetworkParams, CreateOrderBaseParams } from './types'; - export enum Key { - STATE = 'STATE', - FUNDS = 'FUNDS', - USER_INDEX = 'USER_INDEX', - DATE_INDEX = 'DATE_INDEX', - BEST_BY_QUOTE_INDEX = 'BEST_BY_QUOTE_INDEX', - ACTIVE_INDEX = 'ACTIVE_INDEX', - OWNERLESS_INDEX = 'OWNERLESS_INDEX', - CLOSED_INDEX = 'CLOSED_INDEX', - ERROR_INDEX = 'ERROR_INDEX', - UNIQUENESS_CHECK = 'UNIQUENESS_CHECK', - CONTRACT_OWNER = 'CONTRACT_OWNER', - L1_TX_INFO = 'L1_TX_INFO', - INCREMENT_INDEX = 'INCREMENT_INDEX', -} - -export enum ACTIVITY_STATUS { - ACTIVE = 'ACTIVE', - COMPLETED = 'COMPLETED', - CANCELLING = 'CANCELLING', - CANCELLED = 'CANCELLED', - EXPIRED = 'EXPIRED', - ERROR = 'ERROR', -} - -export enum PAYMENT_STATUS { - PAYABLE = 'PAYABLE', - NOT_PAYABLE = 'NOT_PAYABLE', - PAID = 'PAID', + WORD = 'WORD', + SET = 'SET', + BY_LETTER = 'BY_LETTER', } export enum PUBLIC_METHODS { - CREATE_ORDER = '0x01', - CANCEL_ORDER = '0x02', - CHANGE_CONTRACT_OWNER = '0x03', + ADD_WORD = 'ADD_WORD', } export const FEE = { - CREATE_ORDER: 2000000n, - CANCEL_ORDER: 1000000n, + ADD_WORD: 10000n, }; - -export enum CallType { - Accept = 128, - TransferWithSelfTokenFallback = 129, - TransferWithQuoteTokenFallback = 130, -} - -export enum Encoding { - Hex = 128, - Base58 = 129, - Bech32 = 130, - Bech32m = 131, -} - -export enum L1Network {} - -export const BTC_MAIN_NET = { - bech32: 'bc', - pubKeyHash: 0x00, - scriptHash: 0x05, - wif: 0x80, -}; - -export const BTC_TEST_NET: BtcNetworkParams = { - bech32: 'tb', - pubKeyHash: 0x6f, - scriptHash: 0xc4, - wif: 0xef, -}; - -export const BtcShardNetwork = { - btc: BTC_MAIN_NET, - tbtc: BTC_TEST_NET, -} satisfies Record<'btc' | 'tbtc', BtcNetworkParams>; - -export enum BtcNetworkCode { - btc = 128, - tbtc = 129, -} - -export const BtcCodeNetwork = { - [BtcNetworkCode.btc]: BTC_MAIN_NET, - [BtcNetworkCode.tbtc]: BTC_TEST_NET, -}; - -export const OrderBatchFields = [ - 'l1Amount', - 'l1Address', - 'minL1Amount', - 'baseAmount', - 'chainData', -] as const satisfies (keyof CreateOrderBaseParams)[]; diff --git a/packages/contract.cm/src/offchain/shared/index.ts b/packages/contract.cm/src/offchain/shared/index.ts index 9b9acdb..98950cf 100644 --- a/packages/contract.cm/src/offchain/shared/index.ts +++ b/packages/contract.cm/src/offchain/shared/index.ts @@ -1,5 +1,3 @@ export * from './keys'; -export * from './utils'; export * from './constants'; export * from './types'; -export { parseL1TxData } from '../createL1TxData'; diff --git a/packages/contract.cm/src/offchain/shared/keys.ts b/packages/contract.cm/src/offchain/shared/keys.ts index 037c012..6e24ab3 100644 --- a/packages/contract.cm/src/offchain/shared/keys.ts +++ b/packages/contract.cm/src/offchain/shared/keys.ts @@ -1,115 +1,29 @@ -import type { ClaimKey, OrdJson, User } from '@coinweb/contract-kit'; +import type { ClaimKey } from '@coinweb/contract-kit'; import { Key } from './constants'; -import { BtcChainData } from './types'; /* FirstPart */ +export const createWordFirstPart = () => [Key.WORD]; -export const createOrderStateFirstPart = () => [Key.STATE]; +export const createByLetterFirstPart = (letter: string) => [Key.BY_LETTER, letter]; -export const createOrderFundsFirstPart = () => [Key.FUNDS]; - -export const createDateIndexFirstPart = () => [Key.DATE_INDEX]; - -export const createBestByQuoteIndexFirstPart = () => [Key.BEST_BY_QUOTE_INDEX]; - -export const createActiveOrderIndexFirstPart = () => [Key.ACTIVE_INDEX]; - -export const createBestByQuoteActiveIndexFirstPart = () => [Key.BEST_BY_QUOTE_INDEX, Key.ACTIVE_INDEX]; - -export const createUserIndexFirstPart = (user: User) => [Key.USER_INDEX, user]; - -export const createClosedIndexFirstPart = () => [Key.CLOSED_INDEX]; - -export const createOwnerlessIndexFirstPart = () => [Key.OWNERLESS_INDEX]; - -export const createOwnerFirstPart = () => [Key.CONTRACT_OWNER]; - -export const createUniquenessFirstPart = () => [Key.UNIQUENESS_CHECK]; //TODO: Add second element as a type of uniqueness - -export const createL1TxInfoFirstPart = (user: User | null) => [Key.L1_TX_INFO, user]; +export const createSetFirstPart = () => [Key.SET]; /* Key */ -export const createOrderStateKey = (orderId: string) => +export const createWordKey = (id: string) => ({ - first_part: createOrderStateFirstPart(), - second_part: [orderId], + first_part: createWordFirstPart(), + second_part: [id], }) satisfies ClaimKey; -export const createOrderFundsKey = (orderId: string) => +export const createByLetterKey = (letter: string, id: string) => ({ - first_part: createOrderFundsFirstPart(), - second_part: [orderId], + first_part: createByLetterFirstPart(letter), + second_part: [id], }) satisfies ClaimKey; -export const createDateIndexKey = (timestamp: number, orderId: string) => +export const createSetKey = (letter: string) => ({ - first_part: createDateIndexFirstPart(), - second_part: [Number.MAX_SAFE_INTEGER - timestamp, orderId], - }) satisfies ClaimKey; - -export const createBestByQuoteIndexKey = (rate: bigint, orderId: string) => - ({ - first_part: createBestByQuoteIndexFirstPart(), - second_part: [rate.toString(16), orderId], - }) satisfies ClaimKey; - -export const createActiveOrderIndexKey = (timestamp: number, orderId: string) => - ({ - first_part: createActiveOrderIndexFirstPart(), - second_part: [Number.MAX_SAFE_INTEGER - timestamp, orderId], - }) satisfies ClaimKey; - -export const createBestByQuoteActiveIndexKey = (rate: bigint, orderId: string) => - ({ - first_part: createBestByQuoteActiveIndexFirstPart(), - second_part: [rate.toString(16), orderId], - }) satisfies ClaimKey; - -export const createUserIndexKey = (user: User, timestamp: number, orderId: string) => - ({ - first_part: createUserIndexFirstPart(user), - second_part: [Number.MAX_SAFE_INTEGER - timestamp, orderId], - }) satisfies ClaimKey; - -export const createClosedIndexKey = (orderId: string) => - ({ - first_part: createClosedIndexFirstPart(), - second_part: [orderId], - }) satisfies ClaimKey; - -export const createOwnerlessIndexKey = (orderId: string) => - ({ - first_part: createOwnerlessIndexFirstPart(), - second_part: [orderId], - }) satisfies ClaimKey; - -export const createOwnerKey = () => - ({ - first_part: createOwnerFirstPart(), - second_part: [], - }) satisfies ClaimKey; - -export const createUniquenessKey = (data: OrdJson) => - ({ - first_part: createUniquenessFirstPart(), - second_part: data, - }) satisfies ClaimKey; - -export const createBtcUtxoUniquenessKey = (data: BtcChainData) => - ({ - first_part: createUniquenessFirstPart(), - second_part: [data.l1TxId, data.vout], - }) satisfies ClaimKey; - -export const createL1TxKey = (timestamp: number, uniqueId: string, user: User | null) => - ({ - first_part: createL1TxInfoFirstPart(user), - second_part: [Number.MAX_SAFE_INTEGER - timestamp, uniqueId], - }) satisfies ClaimKey; - -export const createIdIncrementIndexKey = () => - ({ - first_part: [Key.INCREMENT_INDEX], - second_part: [Key.INCREMENT_INDEX], + first_part: createSetFirstPart(), + second_part: [letter], }) satisfies ClaimKey; diff --git a/packages/contract.cm/src/offchain/shared/types.ts b/packages/contract.cm/src/offchain/shared/types.ts index 19e84ac..4c2937e 100644 --- a/packages/contract.cm/src/offchain/shared/types.ts +++ b/packages/contract.cm/src/offchain/shared/types.ts @@ -1,124 +1,9 @@ -import type { HexString, User } from '@coinweb/contract-kit'; -import { HistoryAccess } from 'cwap-cm-lib/history/shared'; -import { HexBigInt, BigIntToHex, L1Types } from 'cwap-cm-lib/shared'; - -import { ACTIVITY_STATUS, PAYMENT_STATUS, CallType, BTC_MAIN_NET, OrderBatchFields } from './constants'; - -export type PubKey = string; - -export type OrderStateClaimBody = { - recipient: string; - baseAmount: HexBigInt; - l1Amount: HexBigInt; - minL1Amount: HexBigInt; - createdAt: number; - expirationDate: number; - activityStatus: ACTIVITY_STATUS; - paymentStatus: PAYMENT_STATUS; - funds: HexBigInt; - chainData: ChainData; - txId: string; - error: string | null; - isOwnerless?: boolean; - history: HistoryAccess; +export type SetClaimBody = { + set: string; }; -export type OrderFundsClaimBody = { - owner: User; +export type WordClaimBody = { + word: string; }; -export type ChainData = unknown; - -export type BtcChainData = { - l1TxId: string; - vout: number; - psbt: string; -}; - -export type L1TxInfoData = { - l1TxId: string; - recipientAddress: string; - backPayAddress: string; - amount: HexBigInt; - l2TxId: string; -}; - -type L1TxAcceptPayload = { - baseRecipient: HexString; -}; - -type L1TxTransferBasePayload = { - c2ContractId: HexString; - c2ContractMethod: HexString; - minL1Amount: HexBigInt; - promisedL1Amount: HexBigInt; - l1Recipient: HexString; - l1ChainType: L1Types; -}; - -type L1TxTransferWithSelfFallbackPayload = { - fallbackRecipient: string; -} & L1TxTransferBasePayload; - -type L1TxTransferWithQuoteFallbackPayload = { - fallbackC1ContractId: HexString; - fallbackC1ContractMethod: HexString; -} & L1TxTransferBasePayload; - -export type AcceptData = { - callType: CallType.Accept; -} & L1TxAcceptPayload; - -export type TransferWithSelfFallbackData = { - callType: CallType.TransferWithSelfTokenFallback; -} & L1TxTransferWithSelfFallbackPayload; - -export type TransferWithQuoteFallbackData = { - callType: CallType.TransferWithQuoteTokenFallback; -} & L1TxTransferWithQuoteFallbackPayload; - -export type CreateOrderBaseParams = { - l1Amount: bigint; - l1Address: string; - minL1Amount?: bigint; - baseAmount?: bigint; - chainData?: ChainData; -}; - -export type CreateOrderEvmBaseParams = CreateOrderBaseParams; -export type CreateOrderBtcBaseParams = Omit & { - chainData: BtcChainData; -}; - -export type OrdersBatch = [ - number, - Partial, - Partial[]?, -][]; -export type EvmOrdersBatch = OrdersBatch; -export type BtcOrdersBatch = OrdersBatch; - -export type SerializableOrderParams = BigIntToHex; - -type BatchFields = typeof OrderBatchFields; - -export type SerializedOrderTemplate = { - [I in keyof BatchFields & `${number}`]: BatchFields[I] extends keyof CreateOrderBaseParams - ? BigIntToHex> | null - : never; -}; - -export type SerializedOrderTune = { - [I in keyof BatchFields & `${number}`]?: BatchFields[I] extends keyof CreateOrderBaseParams - ? BigIntToHex - : never; -}; - -export type SerializedOrdersBatch = [OrdersBatch[number][0], SerializedOrderTemplate, SerializedOrderTune[]?][]; - -export type CreateOrderCallArgs = [orders: SerializedOrdersBatch]; - -export type CancelOrderArguments = [ids: string[]]; -export type ChangeContractOwnerArguments = [newOwner: User]; - -export type BtcNetworkParams = typeof BTC_MAIN_NET; +export type AddWordArgs = [string]; diff --git a/packages/contract.cm/src/offchain/shared/utils.ts b/packages/contract.cm/src/offchain/shared/utils.ts deleted file mode 100644 index ecca589..0000000 --- a/packages/contract.cm/src/offchain/shared/utils.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { HexBigInt, BigIntToHex, toHex32, toHex } from 'cwap-cm-lib/shared'; - -import { FEE, OrderBatchFields } from './constants'; -import { OrdersBatch, SerializedOrdersBatch, CreateOrderBaseParams, SerializedOrderTemplate } from './types'; - -export const ordersBatchToList = (ordersBatch: OrdersBatch) => { - return ordersBatch - .map((orderSet) => { - const orders = Array(orderSet[0]) - .fill(orderSet[1]) - .map((order, i) => ({ ...order, ...orderSet[2]?.[i] }) as CreateOrderBaseParams); - - return orders; - }, []) - .flat(); -}; - -export const serializeOrdersBatch = (ordersBatch: OrdersBatch): SerializedOrdersBatch => { - return ordersBatch.map(([count, base, tunes]) => [ - count, - [ - base.l1Amount ? toHex32(base.l1Amount) : null, - base.l1Address ? base.l1Address : null, - base.minL1Amount ? toHex32(base.minL1Amount) : null, - base.baseAmount ? toHex32(base.baseAmount) : null, - base.chainData ? base.chainData : null, - ], - tunes?.map((tune) => - Object.fromEntries( - Object.entries(tune).map(([key, value]) => [ - OrderBatchFields.findIndex((field) => field === key), - ['baseAmount', 'l1Amount', 'minL1Amount'].includes(key) ? toHex(value as bigint) : value, - ]) - ) - ), - ]); -}; - -export const deserializeBatchToList = (ordersBatch: SerializedOrdersBatch): CreateOrderBaseParams[] => { - return ordersBatch - .map((orderSet) => { - const orders = Array(orderSet[0]) - .fill( - Object.fromEntries( - OrderBatchFields.map((field, i) => [ - field, - orderSet[1][i as unknown as keyof SerializedOrderTemplate], - ]).filter(([_, value]) => value !== null) - ) - ) - .map((order: Partial>, i) => ({ - ...order, - ...(orderSet[2]?.[i] && - Object.fromEntries( - Object.entries(orderSet[2][i]).map(([key, value]) => [OrderBatchFields[Number(key)], value]) - )), - })) - .map((order) => - Object.fromEntries( - Object.entries(order).map(([key, value]) => { - return [ - key, - ['baseAmount', 'l1Amount', 'minL1Amount'].includes(key) && value ? BigInt(value as HexBigInt) : value, - ]; - }) - ) - ); - - return orders; - }, []) - .flat() as CreateOrderBaseParams[]; -}; - -export const calculateOrdersListParams = (orders: CreateOrderBaseParams[]) => { - const ordersLength = BigInt(orders.length); - const { l1Amount, minL1Amount, definedBaseAmount, itemsWithUndefinedBaseAmount } = orders.reduce( - (result, order) => { - if (!order.l1Amount) { - throw new Error('creteNewOrderUiCommand: wrong arguments received'); - } - - return { - l1Amount: result.l1Amount + BigInt(order.l1Amount), - minL1Amount: result.minL1Amount + BigInt(order.minL1Amount ?? 0), - definedBaseAmount: result.definedBaseAmount + BigInt(order.baseAmount ?? 0), - itemsWithUndefinedBaseAmount: result.itemsWithUndefinedBaseAmount + (order.baseAmount ? 0n : 1n), - }; - }, - { l1Amount: 0n, minL1Amount: 0n, definedBaseAmount: 0n, itemsWithUndefinedBaseAmount: 0n } - ); - - return { - l1Amount, - minL1Amount, - definedBaseAmount, - itemsWithUndefinedBaseAmount, - fee: FEE.CREATE_ORDER * ordersLength, - isBatch: ordersLength > 1n, - ordersLength, - }; -}; diff --git a/packages/contract.cm/src/offchain/types.ts b/packages/contract.cm/src/offchain/types.ts deleted file mode 100644 index 2d45510..0000000 --- a/packages/contract.cm/src/offchain/types.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - BtcOrdersBatch, - ChainData, - CreateOrderBaseParams, - CreateOrderBtcBaseParams, - CreateOrderEvmBaseParams, - EvmOrdersBatch, - OrdersBatch, - OrderStateClaimBody, -} from './shared'; - -export type CreateOrderData = T & { - contractId: string; - contractOwnerFee: bigint; - accessId?: string; -}; - -export type CreateOrderBtcData = CreateOrderData; -export type CreateOrderEvmData = CreateOrderData; - -export type CreateOrdersData = { - contractId: string; - orders: T; - contractOwnerFee: bigint; - accessId?: string; -}; - -export type CreateOrdersBtcData = CreateOrdersData; -export type CreateOrdersEvmData = CreateOrdersData; - -export type CancelOrderData = { - contractId: string; - orderId: string; - accessId?: string; -}; - -export type CancelOrdersData = { - contractId: string; - orderIds: string[]; - accessId?: string; -}; - -type OrderDataTransformed = { - id: string; - baseAmount: bigint; - l1Amount: bigint; - minL1Amount: bigint; - funds: bigint; - chainData?: ChainData; -}; - -export type OrderData = OrderDataTransformed & Omit; diff --git a/packages/contract.cm/src/offchain/uiCommands.ts b/packages/contract.cm/src/offchain/uiCommands.ts index 33afe4b..49aa006 100644 --- a/packages/contract.cm/src/offchain/uiCommands.ts +++ b/packages/contract.cm/src/offchain/uiCommands.ts @@ -1,130 +1,14 @@ -import { withMetadata } from 'cwap-cm-lib/history/offchain'; -import { HISTORY_ACCESS_FEE } from 'cwap-cm-lib/history/shared'; -import { constructCall, constructUiCommand } from 'cwap-cm-lib/offchain'; +import { constructCall, constructUiCommand } from 'lib/offchain'; -import { FEE, serializeOrdersBatch, PUBLIC_METHODS, ordersBatchToList, calculateOrdersListParams } from './shared'; -import { - CancelOrdersData, - CreateOrdersBtcData, - CreateOrdersEvmData, - CreateOrdersData, - CancelOrderData, - CreateOrderBtcData, - CreateOrderEvmData, -} from './types'; - -const constructCreateOrdersCall = ({ contractId, accessId, orders, contractOwnerFee }: CreateOrdersData) => { - const { l1Amount, minL1Amount, definedBaseAmount, fee } = calculateOrdersListParams(ordersBatchToList(orders)); - - if (l1Amount < minL1Amount) { - throw new Error('creteNewOrderUiCommand: l1Amount is less than minimum value'); - } - - return constructCall( - contractId, - PUBLIC_METHODS.CREATE_ORDER, - withMetadata([serializeOrdersBatch(orders)], accessId), - definedBaseAmount + fee + contractOwnerFee + (accessId ? HISTORY_ACCESS_FEE : 0n) - ); -}; - -export const constructCreateBtcOrdersCall = (data: CreateOrdersBtcData) => { - return constructCreateOrdersCall(data); -}; - -export const constructCreateEvmOrdersCall = (data: CreateOrdersEvmData) => { - return constructCreateOrdersCall(data); -}; - -export const constructCancelOrdersCall = ({ contractId, orderIds, accessId }: CancelOrdersData) => { - return constructCall( - contractId, - PUBLIC_METHODS.CANCEL_ORDER, - withMetadata([orderIds], accessId), - FEE.CANCEL_ORDER * BigInt(orderIds.length) + (accessId ? HISTORY_ACCESS_FEE : 0n) - ); -}; - -export const createOrderBtcUiCommand = ({ - contractId, - baseAmount, - l1Amount, - minL1Amount, - l1Address, - chainData, - accessId, - contractOwnerFee, -}: CreateOrderBtcData) => { - if (!baseAmount) { - throw new Error('createOrderBtcUiCommand: baseAmount is not defined'); - } +import { AddWordArgs, FEE, PUBLIC_METHODS } from './shared'; +export const constructAddWordUiCommand = ({ word, contractId }: { word: string; contractId: string }) => { return constructUiCommand([ - constructCreateOrdersCall({ + constructCall({ contractId, - orders: [ - [ - 1, - { - baseAmount, - l1Amount, - minL1Amount, - l1Address, - chainData, - }, - ], - ], - accessId, - contractOwnerFee, + methodName: PUBLIC_METHODS.ADD_WORD, + methodArgs: [[word] satisfies AddWordArgs], + cost: FEE.ADD_WORD, }), ]); }; - -export const createOrderEvmUiCommand = ({ - contractId, - baseAmount, - l1Amount, - minL1Amount, - l1Address, - accessId, - contractOwnerFee, -}: CreateOrderEvmData) => { - if (!baseAmount) { - throw new Error('createOrderEvmUiCommand: baseAmount is not defined'); - } - - return constructUiCommand([ - constructCreateOrdersCall({ - contractId, - orders: [ - [ - 1, - { - baseAmount, - l1Amount, - minL1Amount, - l1Address, - }, - ], - ], - accessId, - contractOwnerFee, - }), - ]); -}; - -export const createOrdersBtcUiCommand = (data: CreateOrdersBtcData) => { - return constructUiCommand([constructCreateOrdersCall(data)]); -}; - -export const createOrdersEvmUiCommand = (data: CreateOrdersEvmData) => { - return constructUiCommand([constructCreateOrdersCall(data)]); -}; - -export const cancelOrderUiCommand = ({ contractId, orderId, accessId }: CancelOrderData) => { - return constructUiCommand([constructCancelOrdersCall({ contractId, orderIds: [orderId], accessId })]); -}; - -export const cancelOrdersUiCommand = (data: CancelOrdersData) => { - return constructUiCommand([constructCancelOrdersCall(data)]); -}; diff --git a/packages/contract.cm/src/onchain/addWord.ts b/packages/contract.cm/src/onchain/addWord.ts new file mode 100644 index 0000000..ed5173a --- /dev/null +++ b/packages/contract.cm/src/onchain/addWord.ts @@ -0,0 +1,21 @@ +import { constructClaim } from '@coinweb/contract-kit'; +import { storeOp } from 'cwait'; + +import { AddWordArgs, createWordKey } from '../offchain/shared'; + +function hashCode(str: string): string { + let hash = 0; + for (let i = 0, len = str.length; i < len; i++) { + const chr = str.charCodeAt(i); + // eslint-disable-next-line no-bitwise + hash = (hash << 5) - hash + chr; + // eslint-disable-next-line no-bitwise + hash |= 0; + } + return `${hash.toString(16)}`; +} + +export const addWord = async (...[word]: AddWordArgs) => { + const id = hashCode(word); + storeOp(constructClaim(createWordKey(id), { word }, '0x0')); +}; diff --git a/packages/contract.cm/src/onchain/constants.ts b/packages/contract.cm/src/onchain/constants.ts deleted file mode 100644 index 57c6e85..0000000 --- a/packages/contract.cm/src/onchain/constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const PRIVATE_METHODS = { - CREATE_ORDER: 'CREATE_ORDER_PRIVATE', - CREATE_ORDER_INDEXES: 'CREATE_ORDER_INDEXES_PRIVATE', - DEACTIVATE_ORDER: 'DEACTIVATE_ORDER_PRIVATE', - CLOSE_ORDER: 'CLOSE_ORDER_PRIVATE', - HANDLE_BLOCK_TRIGGERED: 'HANDLE_BLOCK_TRIGGERED_PRIVATE', - CHANGE_CONTRACT_OWNER: 'CHANGE_CONTRACT_OWNER_PRIVATE', -}; diff --git a/packages/contract.cm/src/onchain/contract.ts b/packages/contract.cm/src/onchain/contract.ts index dfcf8e7..d44f0ba 100644 --- a/packages/contract.cm/src/onchain/contract.ts +++ b/packages/contract.cm/src/onchain/contract.ts @@ -1,56 +1,30 @@ -import { - ContractHandlers, - MethodCallback, - SELF_REGISTER_HANDLER_NAME, - addMethodHandler, - executeHandler, - selfCallWrapper, -} from '@coinweb/contract-kit'; +import { SELF_REGISTER_HANDLER_NAME, ContractHandlers as CKContractHandlers } from '@coinweb/contract-kit'; import { selfRegisterHandler } from '@coinweb/self-register'; -import { queue, withContractCallLogger } from 'cwap-cm-lib/onchain'; +import { addMethodHandler, ContractHandlers, executeHandler, executor, MethodCallback, selfCallWrapper } from 'cwait'; +import { queue } from 'lib/onchain'; import { PUBLIC_METHODS } from '../offchain/shared'; -import { PRIVATE_METHODS } from './constants'; -import { - cancelOrderPublic, - changeContractOwner, - changeOwnerPublic, - closeOrder, - createOrderIndexes, - createOrder, - createOrderPublic, - deactivateOrder, - handleBlockTriggered, -} from './methods'; +import { addWord } from './addWord'; const addWrappers = (method: MethodCallback): MethodCallback => { - return selfCallWrapper(withContractCallLogger(method)); + return selfCallWrapper(method); }; -export const cwebMain = () => { +const createModule = (): ContractHandlers => { const module: ContractHandlers = { handlers: {} }; - addMethodHandler(module, PUBLIC_METHODS.CREATE_ORDER, addWrappers(createOrderPublic)); - addMethodHandler(module, PRIVATE_METHODS.CREATE_ORDER, addWrappers(createOrder)); - addMethodHandler(module, PRIVATE_METHODS.CREATE_ORDER_INDEXES, addWrappers(createOrderIndexes)); - - addMethodHandler(module, PRIVATE_METHODS.HANDLE_BLOCK_TRIGGERED, addWrappers(handleBlockTriggered)); - - addMethodHandler(module, PUBLIC_METHODS.CANCEL_ORDER, addWrappers(cancelOrderPublic)); - addMethodHandler(module, PRIVATE_METHODS.DEACTIVATE_ORDER, addWrappers(deactivateOrder)); - addMethodHandler(module, PRIVATE_METHODS.CLOSE_ORDER, addWrappers(closeOrder)); - - addMethodHandler(module, PUBLIC_METHODS.CHANGE_CONTRACT_OWNER, addWrappers(changeOwnerPublic)); - addMethodHandler(module, PRIVATE_METHODS.CHANGE_CONTRACT_OWNER, addWrappers(changeContractOwner)); + addMethodHandler(module, PUBLIC_METHODS.ADD_WORD, addWrappers(executor(addWord))); addMethodHandler(module, SELF_REGISTER_HANDLER_NAME, selfRegisterHandler); - queue.applyQueue(module, [ - PUBLIC_METHODS.CANCEL_ORDER, - PUBLIC_METHODS.CHANGE_CONTRACT_OWNER, - PUBLIC_METHODS.CREATE_ORDER, - ]); + queue.applyQueue(module as CKContractHandlers, [PUBLIC_METHODS.ADD_WORD]); - executeHandler(module); + return module; +}; + +export const cwebMain = async () => { + const module = createModule(); + + await executeHandler(module); }; diff --git a/packages/contract.cm/src/onchain/types.ts b/packages/contract.cm/src/onchain/types.ts deleted file mode 100644 index faebb38..0000000 --- a/packages/contract.cm/src/onchain/types.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { HexString, Shard, User } from '@coinweb/contract-kit'; -import { Logs } from 'cwap-cm-lib/onchain'; -import { HexBigInt, L1Types } from 'cwap-cm-lib/shared'; - -import { AcceptData, TransferWithSelfFallbackData, TransferWithQuoteFallbackData } from '../offchain/shared'; - -export type EvmEventClaimBody = { - data: string; - l1_txid: string; -}; - -export type BtcEventClaimBody = { - UtxoBased: { - vout: { - scriptPubKey: { - asm: string; - }; - }[]; - txid: string; - }; -}; - -export type CallContractData = { - c2Contract: string; - c2MethodName: string; - c2Args: [...args: unknown[]]; -}; - -export type L1EventData = { - recipient: HexBigInt; - paidAmount: HexBigInt; - l1TxId: string; -} & (AcceptData | TransferWithSelfFallbackData | TransferWithQuoteFallbackData); - -export type InstanceParameters = { - l1_type: L1Types; - l1_contract_address?: string; - l1_transfer_event_signature?: string; - shard: Shard; - owner: User; - logs?: Logs[]; - owner_min_fee?: HexString; - owner_percentage_fee?: number; - order_life_time: number; - close_order_timeout: number; - jump_contract_id: string; - jump_contract_method: string; - jump_contract_jumps: number; -}; - -export type InstanceParametersForEvm = { - l1_type: L1Types.Evm; - l1_contract_address: string; - l1_transfer_event_signature: string; -} & Omit; - -export type InstanceParametersForBtc = { - l1_type: L1Types.Btc; -} & Omit; - -export type OwnerClaimBody = { - owner: User; - updatedAt: number; -}; diff --git a/packages/cwait/package.json b/packages/cwait/package.json index 4d01cbc..9314871 100644 --- a/packages/cwait/package.json +++ b/packages/cwait/package.json @@ -18,8 +18,8 @@ ], "exports": { ".": { - "types": "./dist/onchain/index.d.ts", - "import": "./dist/onchain/index.js" + "types": "./dist/index.d.ts", + "import": "./dist/index.js" } } } diff --git a/packages/cwait/src/contract-kit/index.ts b/packages/cwait/src/contract-kit/index.ts new file mode 100644 index 0000000..14eaf29 --- /dev/null +++ b/packages/cwait/src/contract-kit/index.ts @@ -0,0 +1,3 @@ +export * from './types'; +export * from './methods'; +export * from './wrappers'; diff --git a/packages/cwait/src/contract-kit/methods.ts b/packages/cwait/src/contract-kit/methods.ts new file mode 100644 index 0000000..7dfbef8 --- /dev/null +++ b/packages/cwait/src/contract-kit/methods.ts @@ -0,0 +1,59 @@ +import { extractContinuations, Context } from '@coinweb/contract-kit'; +import { + getContextCall, + getContextGenesis, + getContextSystem, + getContextTx, + writeToResultFile, +} from '@coinweb/contract-kit/dist/esm/context'; +import { getMethodName } from '@coinweb/contract-kit/dist/esm/method'; + +import { ContractHandlers, MethodCallback } from './types'; + +/** + * Adds a method handler for a specific method name. + * @param contract_module - Contract module containing the method. + * @param methodName - The name of the method. + * @param handler - The method callback to add. + */ +export function addMethodHandler(contract_module: ContractHandlers, methodName: string, handler: MethodCallback): void { + contract_module.handlers[methodName] = handler; +} + +/** + * Retrieves the method handler for a specific method name. + * @param contract_module - Contract module containing the method. + * @param methodName - The name of the method. + * @returns The method callback for the specified method name. + * @throws Will throw an error if no handler is specified for the method name. + */ +export function getMethodHandler(contract_module: ContractHandlers, methodName: string): MethodCallback { + const handler = contract_module.handlers[methodName]; + if (!handler) { + throw Error('Handler not specified for this method name'); + } + return handler; +} + +/** + * Executes the handler for the method specified in the transaction context. + * The results (new transactions) are written to the result file. + * @param contractModule - Contract module containing the method. + */ +export async function executeHandler(contractModule: ContractHandlers): Promise { + const contextTx = getContextTx(); + const contextCall = getContextCall(); + const genesis = getContextGenesis(); + const system = getContextSystem(); + const context: Context = { + tx: contextTx, + call: contextCall, + genesis, + system, + continuations: extractContinuations(contextTx), + }; + const method = getMethodName(context); + const handler = getMethodHandler(contractModule, method); + const txs = await handler(context); + writeToResultFile(txs); +} diff --git a/packages/cwait/src/contract-kit/quickjs/os.d.ts b/packages/cwait/src/contract-kit/quickjs/os.d.ts new file mode 100644 index 0000000..b59d7cb --- /dev/null +++ b/packages/cwait/src/contract-kit/quickjs/os.d.ts @@ -0,0 +1,3 @@ +declare namespace os { + function readdir(path: string): [any[], number]; +} diff --git a/packages/cwait/src/contract-kit/quickjs/std.d.ts b/packages/cwait/src/contract-kit/quickjs/std.d.ts new file mode 100644 index 0000000..5b3b5ca --- /dev/null +++ b/packages/cwait/src/contract-kit/quickjs/std.d.ts @@ -0,0 +1,3 @@ +declare namespace std { + function open(path: string, mode: string): any; +} diff --git a/packages/cwait/src/contract-kit/store.d.ts b/packages/cwait/src/contract-kit/store.d.ts new file mode 100644 index 0000000..e68f2ef --- /dev/null +++ b/packages/cwait/src/contract-kit/store.d.ts @@ -0,0 +1,11 @@ +declare module '@coinweb/contract-kit/dist/esm/operations/store' { + export * from '@coinweb/contract-kit/dist/types/operations/store'; +} + +declare module '@coinweb/contract-kit/dist/esm/context' { + export * from '@coinweb/contract-kit/dist/types/context'; +} + +declare module '@coinweb/contract-kit/dist/esm/method' { + export * from '@coinweb/contract-kit/dist/types/method'; +} diff --git a/packages/cwait/src/contract-kit/types.ts b/packages/cwait/src/contract-kit/types.ts new file mode 100644 index 0000000..fe7d359 --- /dev/null +++ b/packages/cwait/src/contract-kit/types.ts @@ -0,0 +1,7 @@ +import { Context, NewTx } from '@coinweb/contract-kit'; + +export type MethodCallback = (context: Context) => Promise | NewTx[]; + +export type ContractHandlers = { + handlers: { [key: string]: MethodCallback }; +}; diff --git a/packages/cwait/src/contract-kit/wrappers.ts b/packages/cwait/src/contract-kit/wrappers.ts new file mode 100644 index 0000000..ff5b595 --- /dev/null +++ b/packages/cwait/src/contract-kit/wrappers.ts @@ -0,0 +1,17 @@ +import { Context, isSelfCall } from '@coinweb/contract-kit'; + +import { MethodCallback } from './types'; + +/** + * Wraps a method callback to ensure that it can only be called by the contract itself. + * @param handler - The method callback to wrap. + * @returns A new method callback that throws an error if the call is not from the contract itself. + */ +export function selfCallWrapper(handler: MethodCallback): MethodCallback { + return async (context: Context) => { + if (!isSelfCall(context)) { + throw new Error('Only contract itself can call it'); + } + return handler(context); + }; +} diff --git a/packages/cwait/src/index.ts b/packages/cwait/src/index.ts new file mode 100644 index 0000000..55987ee --- /dev/null +++ b/packages/cwait/src/index.ts @@ -0,0 +1,3 @@ +export * from './contract-kit'; +export * from './onchain'; +export * from './types'; diff --git a/packages/cwait/src/onchain/context/context.ts b/packages/cwait/src/onchain/context/context.ts index 16d950e..69faa16 100644 --- a/packages/cwait/src/onchain/context/context.ts +++ b/packages/cwait/src/onchain/context/context.ts @@ -2,28 +2,26 @@ import { constructContractIssuer, Context, extractUser, getAuthenticated, getCon let rawContext: Context | null = null; -export const setContext = (ctx: Context) => { +export const setRawContext = (ctx: Context) => { rawContext = ctx; }; -export const getContext = () => rawContext; - -const useContext = (cb: (ctx: Context) => T) => { +export const getRawContext = () => { if (!rawContext) { throw new Error('Context not set'); } - return cb(rawContext); + return rawContext; }; export const context = { get issuer() { - return useContext((ctx) => constructContractIssuer(getContractId(ctx.tx))); + return constructContractIssuer(getContractId(getRawContext().tx)); }, get authenticated() { - return useContext((ctx) => getAuthenticated(ctx.tx)); + return getAuthenticated(getRawContext().tx); }, get user() { - return useContext((ctx) => extractUser(getAuthenticated(ctx.tx))); + return extractUser(getAuthenticated(getRawContext().tx)); }, }; diff --git a/packages/cwait/src/onchain/executor.ts b/packages/cwait/src/onchain/executor.ts new file mode 100644 index 0000000..fca42c3 --- /dev/null +++ b/packages/cwait/src/onchain/executor.ts @@ -0,0 +1,84 @@ +import { + Context, + extractContractArgs, + NewTx, + ResolvedOperation, + getMethodArguments, + constructContinueTx, + constructContractRef, +} from '@coinweb/contract-kit'; +import { getCallParameters, queue } from 'lib/onchain'; + +import { context, getRawContext, setRawContext } from './context'; +import { getAwaitedOps } from './ops/awaited'; +import { pushResolvedOp } from './ops/resolved'; + +let abortExecution: (() => void) | null = null; + +const handleState = () => { + const ctx = getRawContext(); + + const resolvedOps = extractContractArgs(ctx.tx); + const currentArgs = getMethodArguments(ctx) as [unknown, unknown[], ResolvedOperation[]]; + + const initialArgs = currentArgs[1]; + const allResolvedOps = [...currentArgs[2], ...resolvedOps]; + + pushResolvedOp(allResolvedOps); + + return { args: initialArgs, methodName: currentArgs[0] as string, ops: allResolvedOps }; +}; + +export const executor = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (method: (...args: any[]) => Promise) => + async (ctx: Context): Promise => { + setRawContext(ctx); + + const { args, methodName, ops } = handleState(); + + const execution = new Promise((resolve, reject) => { + abortExecution = resolve; + + method(...args).then(resolve, reject); + }); + + await execution; + + const { authInfo, availableCweb } = getCallParameters(ctx); + + const awaitedOps = getAwaitedOps(); + + if (!awaitedOps.length) { + return queue.gateway.unlock(ctx); + } + + const txFee = 700n + BigInt(awaitedOps.length) * 100n; + + return [ + constructContinueTx(ctx, awaitedOps, [ + { + callInfo: { + ref: constructContractRef(context.issuer, []), + methodInfo: { + methodName, + methodArgs: [args, ops], + }, + contractInfo: { + providedCweb: availableCweb - txFee, + authenticated: authInfo, + }, + contractArgs: [], + }, + }, + ]), + ]; + }; + +export const abort = () => { + if (!abortExecution) { + throw new Error('Abort not found'); + } + + abortExecution(); +}; diff --git a/packages/cwait/src/onchain/index.ts b/packages/cwait/src/onchain/index.ts index e69de29..5e5fb69 100644 --- a/packages/cwait/src/onchain/index.ts +++ b/packages/cwait/src/onchain/index.ts @@ -0,0 +1,3 @@ +export * from './context'; +export * from './executor'; +export * from './ops'; diff --git a/packages/cwait/src/onchain/ops/read.ts b/packages/cwait/src/onchain/ops/read.ts index 2cb835f..fbf2492 100644 --- a/packages/cwait/src/onchain/ops/read.ts +++ b/packages/cwait/src/onchain/ops/read.ts @@ -2,7 +2,7 @@ import { Claim, ClaimKey, constructRead, extractRead, isResolvedRead } from '@co import { TypedClaim } from '../../types'; import { context } from '../context'; -import { signal } from '../signal'; +import { abort } from '../executor'; import { pushAwaitedOp } from './awaited'; import { shiftResolvedOp } from './resolved'; @@ -16,12 +16,12 @@ export const readOp = (key: ClaimKey) => { throw new Error('Read operation not found'); } - return new Promise((resolve, reject) => { + return new Promise((resolve) => { if (isOp) { const claim = op && ((extractRead(op)?.[0] ?? null) as TClaim | null); resolve(claim); } else { - reject(signal); + abort(); } }); }; diff --git a/packages/cwait/src/onchain/ops/store.ts b/packages/cwait/src/onchain/ops/store.ts index 77c9dae..0357ec5 100644 --- a/packages/cwait/src/onchain/ops/store.ts +++ b/packages/cwait/src/onchain/ops/store.ts @@ -1,7 +1,7 @@ import { Claim, constructStore, isResolvedStore } from '@coinweb/contract-kit'; -import { extractStore } from '@coinweb/contract-kit/dist/types/operations/store'; +import { extractStore } from '@coinweb/contract-kit/dist/esm/operations/store'; -import { signal } from '../signal'; +import { abort } from '../executor'; import { pushAwaitedOp } from './awaited'; import { shiftResolvedOp } from './resolved'; @@ -21,11 +21,11 @@ export const storeOp = (claim: Claim) => { throw new Error('Wrong store operation'); } - return new Promise((resolve, reject) => { + return new Promise((resolve) => { if (isOp) { resolve(result); } else { - reject(signal); + abort(); } }); }; diff --git a/packages/cwait/src/onchain/ops/take.ts b/packages/cwait/src/onchain/ops/take.ts index e2f674c..9a2f9d8 100644 --- a/packages/cwait/src/onchain/ops/take.ts +++ b/packages/cwait/src/onchain/ops/take.ts @@ -1,7 +1,7 @@ import { constructTake, extractTake, isResolvedTake, Claim, ClaimKey } from '@coinweb/contract-kit'; import { TypedClaim } from '../../types'; -import { signal } from '../signal'; +import { abort } from '../executor'; import { pushAwaitedOp } from './awaited'; import { shiftResolvedOp } from './resolved'; @@ -15,12 +15,12 @@ export const takeOp = (key: ClaimKey) => { throw new Error('Take operation not found'); } - return new Promise((resolve, reject) => { + return new Promise((resolve) => { if (isOp) { const claim = op && extractTake(op); resolve(claim as TClaim | null); } else { - reject(signal); + abort(); } }); }; diff --git a/packages/cwait/src/onchain/signal.ts b/packages/cwait/src/onchain/signal.ts deleted file mode 100644 index eb285dc..0000000 --- a/packages/cwait/src/onchain/signal.ts +++ /dev/null @@ -1 +0,0 @@ -export const signal = Symbol('signal'); diff --git a/packages/lib/src/offchain/features/uiCommands/constructCall.ts b/packages/lib/src/offchain/features/uiCommands/constructCall.ts index 7e6670d..cba5e5a 100644 --- a/packages/lib/src/offchain/features/uiCommands/constructCall.ts +++ b/packages/lib/src/offchain/features/uiCommands/constructCall.ts @@ -1,13 +1,45 @@ -import { ContractCall, constructContractIssuer, prepareQueueContractCall } from '@coinweb/contract-kit'; +import { + ContractCall, + constructContractIssuer, + constructSelfRegisterKey, + constructSingleReadClaim, + prepareQueueContractCall, +} from '@coinweb/contract-kit'; -export const constructCall = ( - contractId: string, - methodName: string, - methodArgs: unknown[], - cost: bigint, - auth: boolean = true -): ContractCall => { +import { toHex } from '../../../shared'; + +export const constructCall = ({ + contractId, + methodName, + methodArgs, + cost, + auth = true, + withQueue = true, +}: { + contractId: string; + methodName: string; + methodArgs: unknown[]; + cost: bigint; + auth?: boolean; + withQueue?: boolean; +}): ContractCall => { const issuer = constructContractIssuer(contractId); + if (!withQueue) { + const contractCall: ContractCall = { + contract_input: { + data: [methodName, ...methodArgs], + cost: toHex(cost), + authenticated: auth, + }, + contract_ref: { + explicit: [], + stored: [constructSingleReadClaim(constructContractIssuer(contractId), constructSelfRegisterKey())], + }, + }; + + return contractCall; + } + return prepareQueueContractCall(issuer, { methodName, methodArgs }, cost, auth); }; diff --git a/packages/lib/src/onchain/contract/createHandler.ts b/packages/lib/src/onchain/contract/createHandler.ts index b8e9670..9e5a072 100644 --- a/packages/lib/src/onchain/contract/createHandler.ts +++ b/packages/lib/src/onchain/contract/createHandler.ts @@ -10,7 +10,6 @@ import { QueueInput, } from '@coinweb/contract-kit'; import { DataUnverified } from '@coinweb/contract-kit/dist/types/operations/data'; -import { HexBigInt, toHex32 } from 'cwap-cm-lib/shared'; import { checkIsNewHistoryAccessId, @@ -21,6 +20,7 @@ import { validateMetadata, } from '../../module/history/onchain'; import { ContractCallMetadataInterface, HISTORY_CODES, HistoryItem } from '../../module/history/shared'; +import { HexBigInt, toHex32 } from '../../shared'; import { TX_STATUS_ERROR } from '../constants'; import { Exception } from '../features/exception'; import { queue, queueUnlockFee } from '../features/queue'; diff --git a/packages/lib/src/onchain/features/logger.ts b/packages/lib/src/onchain/features/logger.ts index 123c194..f1385fb 100644 --- a/packages/lib/src/onchain/features/logger.ts +++ b/packages/lib/src/onchain/features/logger.ts @@ -16,8 +16,8 @@ import { MethodCallback, ResolvedOperation, } from '@coinweb/contract-kit'; -import { toHex32 } from 'cwap-cm-lib/shared'; +import { toHex32 } from '../../shared'; import { Logs } from '../constants'; const normalizeData = (data: unknown) => { diff --git a/packages/lib/src/onchain/features/queue.ts b/packages/lib/src/onchain/features/queue.ts index 615af88..f4fb5b2 100644 --- a/packages/lib/src/onchain/features/queue.ts +++ b/packages/lib/src/onchain/features/queue.ts @@ -1,5 +1,5 @@ import { Queue } from '@coinweb/contract-kit'; -export const queue = new Queue({ name: 'c1', executionDepth: 2 }); +export const queue = new Queue({ name: 'queue', executionDepth: 2 }); export const queueUnlockFee = 3000n; diff --git a/packages/ui/.env b/packages/ui/.env index 537cec6..cfd1657 100644 --- a/packages/ui/.env +++ b/packages/ui/.env @@ -1,2 +1,4 @@ VITE_API_URL='https://api-cloud.coinweb.io/wallet' VITE_EXPLORER_URL='https://explorer.coinweb.io' + +VITE_CONTRACT_ADDRESS="0xc8ed7479667e1b1a9223db34e6a91b3970c7f82686ed0712fce89117a10b77ec" \ No newline at end of file diff --git a/packages/ui/package.json b/packages/ui/package.json index 0a816af..5dde068 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -17,6 +17,7 @@ "@coinweb/webapp-library": "0.1.6", "axios": "^1.7.2", "contract.cm": "workspaces:*", + "qrcode.react": "^4.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.22.3", diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index 388d5cc..2b6a9f1 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -1,7 +1,56 @@ +import { useState } from 'react'; + +import { constructAddWordUiCommand } from 'contract.cm'; +import { QRCodeSVG } from 'qrcode.react'; + export const App = () => { + const [word, setWord] = useState(''); + const [qrCode, setQrCode] = useState(''); + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setQrCode(constructAddWordUiCommand({ word, contractId: import.meta.env.VITE_CONTRACT_ADDRESS ?? '' })); + }; + return (
-

Hello World

+
+
+ + setWord(e.target.value)} + /> + +
+
+
+ { + navigator.clipboard.writeText(qrCode).catch((err) => { + console.error(err); + }); + }} + value={qrCode} + size={320} + level="L" + className="hover:cursor-pointer" + bgColor="transparent" + fgColor="black" + /> +
); }; diff --git a/scripts/update-ui-env.js b/scripts/update-ui-env.js index 1b15ea8..34199da 100644 --- a/scripts/update-ui-env.js +++ b/scripts/update-ui-env.js @@ -4,24 +4,11 @@ import path from 'node:path'; import YAML from 'yaml'; -const BaseContractTemplate = 'dex-app.cm'; -const MarketMakerContractTemplate = 'market-maker.cm'; - const ParamsPatternMatching = [ - ['VITE_L2_OWNER_MIN_FEE', 'owner_min_fee'], - ['VITE_L2_OWNER_PERCENTAGE_FEE', 'owner_percentage_fee'], - ['VITE_L2_COLLATERAL_PERCENTAGE', 'collateral_percentage'], // ['VITE_L1_CONTRACT_ADDRESS', 'l1_contract_address'], ]; -const ContractIdPattern = 'VITE_L2_CONTRACT_ADDRESS'; - -const TemplatePatternMatching = [ - ['_BASE_', BaseContractTemplate], - ['_MAKER_', MarketMakerContractTemplate], -]; - -const Variants = [['eth'], ['bnb'], ['btc'], ['trx'], ['eth', 'usdt'], ['bnb', 'usdt'], ['trx', 'usdt']]; +const ContractIdPattern = 'VITE_CONTRACT_ADDRESS'; const updated = []; @@ -51,48 +38,28 @@ function updateValue(line, value) { } function updateLine(line, parameters, index) { - const templatePattern = TemplatePatternMatching.find(([pattern]) => isMatch(line, pattern)); - const variant = Variants.filter((variant) => variant.every((pattern) => isMatch(line, pattern))).sort( - ({ length: a }, { length: b }) => (a < b ? 1 : -1) - )[0]; - - if (!templatePattern || !variant) { - return line; - } - const paramPattern = ParamsPatternMatching.find(([pattern]) => isMatch(line, pattern)); if (paramPattern) { - const instanceParameters = parameters.find( - ({ alias, template }) => - isMatch(template, templatePattern[1]) && variant.every((pattern) => isMatch(alias, pattern)) - ); + const value = Object.entries(parameters.parameters.content).find(([name]) => isMatch(name, paramPattern[1]))?.[1]; - if (instanceParameters) { - const value = Object.entries(instanceParameters.parameters.content).find(([name]) => - isMatch(name, paramPattern[1]) - )?.[1]; + if (value !== undefined) { + const updatedLine = updateValue(line, value); - if (value !== undefined) { - const updatedLine = updateValue(line, value); - - if (line !== updatedLine) { - updated.push(updatedLine); - } - - return updatedLine; + if (line !== updatedLine) { + updated.push(updatedLine); } + + return updatedLine; } } if (isMatch(line, ContractIdPattern)) { - const templates = Object.entries(index).filter(([name]) => isMatch(name, templatePattern[1])); - const instances = templates.find((template) => template[1].target_instances.length)?.[1].target_instances; + const instances = Object.entries(index).find((template) => template[1].target_instances.length)?.[1] + .target_instances; if (instances?.length) { - const contractId = instances.find(({ alias }) => - variant.every((pattern) => isMatch(alias, pattern)) - )?.instance_id; + const contractId = instances[0]?.instance_id; if (contractId !== undefined) { const updatedLine = updateValue(line, '0x' + contractId); @@ -151,7 +118,7 @@ function printResults() { throw new Error('Cannot find config for profile', profile); } - const envFilePath = path.resolve('packages/dapp-ui', envFileName); + const envFilePath = path.resolve('packages/ui', envFileName); const env = fs.readFileSync(envFilePath, 'utf-8'); if (!env) { diff --git a/yarn.lock b/yarn.lock index c58aa53..700c774 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6970,6 +6970,15 @@ __metadata: languageName: node linkType: hard +"qrcode.react@npm:^4.2.0": + version: 4.2.0 + resolution: "qrcode.react@npm:4.2.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/68c691d130e5fda2f57cee505ed7aea840e7d02033100687b764601f9595e1116e34c13876628a93e1a5c2b85e4efc27d30b2fda72e2050c02f3e1c4e998d248 + languageName: node + linkType: hard + "qs@npm:^6.12.3": version: 6.14.0 resolution: "qs@npm:6.14.0" @@ -8702,6 +8711,7 @@ __metadata: jsdom: "npm:^24.0.0" postcss: "npm:^8.4.39" postcss-cli: "npm:^11.0.0" + qrcode.react: "npm:^4.2.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" react-router-dom: "npm:^6.22.3"