feat: implement minimum stand

This commit is contained in:
Alex 2025-04-03 10:55:01 +03:00
parent 84a1eda9e3
commit b6f07adb66
43 changed files with 448 additions and 888 deletions

View File

@ -10,8 +10,15 @@
includes: includes:
- url: >- - url: >-
https://gitlab.com/coinweb/cweb-dapp-index/-/raw/b534ebc0dbe7faa3458f11e2bc19553ab70bc317/dapp_lock_index.yaml https://gitlab.com/coinweb/cweb-dapp-index/-/raw/e7180f56cbcf7c04fbdee487f271500cff637067/dapp_lock_index.yaml
blake3: 48c6cd4b1b9252f2513efca47a0a8bd2f61417c6adc6bcd9d7e30521e735545e 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: {} interpreters: {}
@ -24,4 +31,6 @@ contract_templates:
contract_instances: contract_instances:
- alias: cwait-contract 0.0.1-devnet - alias: cwait-contract 0.0.1-devnet
template: contract.cm v0.0.1 template: contract.cm v0.0.1
parameters:
content: []

View File

@ -10,8 +10,8 @@
includes: includes:
- url: >- - url: >-
https://gitlab.com/coinweb/cweb-dapp-index/-/raw/35d7542b81d884adb899ea95f1433fe59b012a6c/dapp_lock_index.yaml https://gitlab.com/coinweb/cweb-dapp-index/-/raw/e7180f56cbcf7c04fbdee487f271500cff637067/dapp_lock_index.yaml
blake3: 0e8f57c2c939f9cccf6912c2fb7994bb7095a1f10bef585296dba4263eb76211 blake3: 321b1f88930aead7fe47961c8664005b3441f6502824769bf21eb06c3e5aaba4
interpreters: {} interpreters: {}
@ -23,4 +23,5 @@ contract_templates:
contract_instances: contract_instances:
- alias: cwait-contract 0.0.1-prod - alias: cwait-contract 0.0.1-prod
template: contract.cm v0.0.1 template: contract.cm v0.0.1
parameters:
content: []

View File

@ -10,8 +10,8 @@
includes: includes:
- url: >- - url: >-
https://gitlab.com/coinweb/cweb-dapp-index/-/raw/5d52c2c4c39fbe74b7e86e9a9c463240df69b01d/dapp_lock_index.yaml https://gitlab.com/coinweb/cweb-dapp-index/-/raw/e7180f56cbcf7c04fbdee487f271500cff637067/dapp_lock_index.yaml
blake3: a576de68122e704240acc7efe813d52cecd1519fab1078db537ed4b59da0f9ba blake3: 321b1f88930aead7fe47961c8664005b3441f6502824769bf21eb06c3e5aaba4
use: use:
- dex-app.cm v0.0.67+test - dex-app.cm v0.0.67+test
@ -29,3 +29,5 @@ contract_templates:
contract_instances: contract_instances:
- alias: cwait-contract 0.0.1-test - alias: cwait-contract 0.0.1-test
template: contract.cm v0.0.1 template: contract.cm v0.0.1
parameters:
content: []

View File

@ -121,6 +121,8 @@ export default tseslint.config(
allowTypedFunctionExpressions: true, allowTypedFunctionExpressions: true,
}, },
], ],
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/require-await': 'off',
}, },
}, },

View File

@ -26,5 +26,8 @@
"bech32": "2.0.0", "bech32": "2.0.0",
"bs58": "6.0.0" "bs58": "6.0.0"
}, },
"main": "cweb_dist/offchain/index.js" "main": "cweb_dist/offchain/index.js",
"engines": {
"cweb_interpreter": "a344c6003922f9e44385f6e8234a7d2567d9a676b14330ad3b42cbd1948a92bf"
}
} }

View File

@ -1,115 +1,41 @@
import { ClaimKey, User } from '@coinweb/contract-kit'; import { ClaimKey } from '@coinweb/contract-kit';
import { Client } from 'cwap-cm-lib/offchain'; import { Client } from 'lib/offchain';
import { import { SetClaimBody, WordClaimBody } from './shared';
createActiveOrderIndexFirstPart, import { createWordFirstPart, createSetKey, createWordKey, createByLetterFirstPart } from './shared/keys';
createBestByQuoteActiveIndexFirstPart,
createBestByQuoteIndexFirstPart,
createDateIndexFirstPart,
createL1TxInfoFirstPart,
createOrderStateKey,
createOwnerlessIndexFirstPart,
createUniquenessFirstPart,
createUserIndexFirstPart,
} from './shared/keys';
import { BtcChainData, L1TxInfoData, OrderStateClaimBody } from './shared/types';
import { OrderData } from './types';
export const getActiveOrderIds = async (client: Client): Promise<string[]> => { export const getWord = async (client: Client, id: string) => {
const claimsResponse = await client.fetchClaims(createActiveOrderIndexFirstPart(), null); const key = createWordKey(id);
return claimsResponse.map(({ content }) => ((content.key as ClaimKey).second_part as [number, string])[1]);
};
export const getBestOrderIds = async (client: Client): Promise<string[]> => {
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<string[]> => {
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<string[]> => {
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<string[]> => {
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<string[]> => {
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<Pick<BtcChainData, 'l1TxId' | 'vout'>[]> => {
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);
const claimResponse = (await client.fetchClaims(key.first_part, key.second_part))[0]; const claimResponse = (await client.fetchClaims(key.first_part, key.second_part))[0];
if (!claimResponse) { const data = claimResponse.content.body as WordClaimBody;
throw new Error('Order not found');
}
const data = claimResponse.content.body as OrderStateClaimBody; return data.word;
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;
}; };
export const getOwnerlessOrders = async (client: Client) => { export const getWords = async (client: Client): Promise<string[]> => {
const ids = await getOwnerlessOrderIds(client); const claimsResponse = await client.fetchClaims(createWordFirstPart(), null);
try { return claimsResponse.map(({ content }) => (content.body as WordClaimBody).word);
const orders = await Promise.all(ids.map((id) => getOrderById(client, id)));
return orders;
} catch (e) {
console.error({ e });
return [];
}
}; };
export const getL1TxInfo = async (client: Client, user: User): Promise<L1TxInfoData[]> => { export const getWordsByLetter = async (client: Client, letter: string): Promise<string[]> => {
const claimsResponse = await client.fetchClaims(createL1TxInfoFirstPart(user), null); 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<string> => {
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;
}; };

View File

@ -1,6 +1,3 @@
export * from './api'; export * from './api';
export * from './uiCommands'; export * from './uiCommands';
export * from './types';
export * from './shared'; export * from './shared';
export * from './createL1TxData';
export * from './reexport';

View File

@ -1,95 +1,13 @@
import { BtcNetworkParams, CreateOrderBaseParams } from './types';
export enum Key { export enum Key {
STATE = 'STATE', WORD = 'WORD',
FUNDS = 'FUNDS', SET = 'SET',
USER_INDEX = 'USER_INDEX', BY_LETTER = 'BY_LETTER',
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',
} }
export enum PUBLIC_METHODS { export enum PUBLIC_METHODS {
CREATE_ORDER = '0x01', ADD_WORD = 'ADD_WORD',
CANCEL_ORDER = '0x02',
CHANGE_CONTRACT_OWNER = '0x03',
} }
export const FEE = { export const FEE = {
CREATE_ORDER: 2000000n, ADD_WORD: 10000n,
CANCEL_ORDER: 1000000n,
}; };
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)[];

View File

@ -1,5 +1,3 @@
export * from './keys'; export * from './keys';
export * from './utils';
export * from './constants'; export * from './constants';
export * from './types'; export * from './types';
export { parseL1TxData } from '../createL1TxData';

View File

@ -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 { Key } from './constants';
import { BtcChainData } from './types';
/* FirstPart */ /* 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 createSetFirstPart = () => [Key.SET];
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];
/* Key */ /* Key */
export const createOrderStateKey = (orderId: string) => export const createWordKey = (id: string) =>
({ ({
first_part: createOrderStateFirstPart(), first_part: createWordFirstPart(),
second_part: [orderId], second_part: [id],
}) satisfies ClaimKey; }) satisfies ClaimKey;
export const createOrderFundsKey = (orderId: string) => export const createByLetterKey = (letter: string, id: string) =>
({ ({
first_part: createOrderFundsFirstPart(), first_part: createByLetterFirstPart(letter),
second_part: [orderId], second_part: [id],
}) satisfies ClaimKey; }) satisfies ClaimKey;
export const createDateIndexKey = (timestamp: number, orderId: string) => export const createSetKey = (letter: string) =>
({ ({
first_part: createDateIndexFirstPart(), first_part: createSetFirstPart(),
second_part: [Number.MAX_SAFE_INTEGER - timestamp, orderId], second_part: [letter],
}) 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],
}) satisfies ClaimKey; }) satisfies ClaimKey;

View File

@ -1,124 +1,9 @@
import type { HexString, User } from '@coinweb/contract-kit'; export type SetClaimBody = {
import { HistoryAccess } from 'cwap-cm-lib/history/shared'; set: string;
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 OrderFundsClaimBody = { export type WordClaimBody = {
owner: User; word: string;
}; };
export type ChainData = unknown; export type AddWordArgs = [string];
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<CreateOrderBaseParams, 'chainData'> & {
chainData: BtcChainData;
};
export type OrdersBatch<T extends CreateOrderBaseParams = CreateOrderBaseParams> = [
number,
Partial<T>,
Partial<T>[]?,
][];
export type EvmOrdersBatch = OrdersBatch<CreateOrderEvmBaseParams>;
export type BtcOrdersBatch = OrdersBatch<CreateOrderBtcBaseParams>;
export type SerializableOrderParams = BigIntToHex<CreateOrderBaseParams>;
type BatchFields = typeof OrderBatchFields;
export type SerializedOrderTemplate = {
[I in keyof BatchFields & `${number}`]: BatchFields[I] extends keyof CreateOrderBaseParams
? BigIntToHex<Required<CreateOrderBaseParams[BatchFields[I]]>> | null
: never;
};
export type SerializedOrderTune = {
[I in keyof BatchFields & `${number}`]?: BatchFields[I] extends keyof CreateOrderBaseParams
? BigIntToHex<CreateOrderBaseParams[BatchFields[I]]>
: 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;

View File

@ -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<BigIntToHex<CreateOrderBaseParams>>, 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,
};
};

View File

@ -1,52 +0,0 @@
import {
BtcOrdersBatch,
ChainData,
CreateOrderBaseParams,
CreateOrderBtcBaseParams,
CreateOrderEvmBaseParams,
EvmOrdersBatch,
OrdersBatch,
OrderStateClaimBody,
} from './shared';
export type CreateOrderData<T extends CreateOrderBaseParams = CreateOrderBaseParams> = T & {
contractId: string;
contractOwnerFee: bigint;
accessId?: string;
};
export type CreateOrderBtcData = CreateOrderData<CreateOrderBtcBaseParams>;
export type CreateOrderEvmData = CreateOrderData<CreateOrderEvmBaseParams>;
export type CreateOrdersData<T extends OrdersBatch = OrdersBatch> = {
contractId: string;
orders: T;
contractOwnerFee: bigint;
accessId?: string;
};
export type CreateOrdersBtcData = CreateOrdersData<BtcOrdersBatch>;
export type CreateOrdersEvmData = CreateOrdersData<EvmOrdersBatch>;
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<OrderStateClaimBody, keyof OrderDataTransformed>;

View File

@ -1,130 +1,14 @@
import { withMetadata } from 'cwap-cm-lib/history/offchain'; import { constructCall, constructUiCommand } from 'lib/offchain';
import { HISTORY_ACCESS_FEE } from 'cwap-cm-lib/history/shared';
import { constructCall, constructUiCommand } from 'cwap-cm-lib/offchain';
import { FEE, serializeOrdersBatch, PUBLIC_METHODS, ordersBatchToList, calculateOrdersListParams } from './shared'; import { AddWordArgs, FEE, PUBLIC_METHODS } 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');
}
export const constructAddWordUiCommand = ({ word, contractId }: { word: string; contractId: string }) => {
return constructUiCommand([ return constructUiCommand([
constructCreateOrdersCall({ constructCall({
contractId, contractId,
orders: [ methodName: PUBLIC_METHODS.ADD_WORD,
[ methodArgs: [[word] satisfies AddWordArgs],
1, cost: FEE.ADD_WORD,
{
baseAmount,
l1Amount,
minL1Amount,
l1Address,
chainData,
},
],
],
accessId,
contractOwnerFee,
}), }),
]); ]);
}; };
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)]);
};

View File

@ -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'));
};

View File

@ -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',
};

View File

@ -1,56 +1,30 @@
import { import { SELF_REGISTER_HANDLER_NAME, ContractHandlers as CKContractHandlers } from '@coinweb/contract-kit';
ContractHandlers,
MethodCallback,
SELF_REGISTER_HANDLER_NAME,
addMethodHandler,
executeHandler,
selfCallWrapper,
} from '@coinweb/contract-kit';
import { selfRegisterHandler } from '@coinweb/self-register'; 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 { PUBLIC_METHODS } from '../offchain/shared';
import { PRIVATE_METHODS } from './constants'; import { addWord } from './addWord';
import {
cancelOrderPublic,
changeContractOwner,
changeOwnerPublic,
closeOrder,
createOrderIndexes,
createOrder,
createOrderPublic,
deactivateOrder,
handleBlockTriggered,
} from './methods';
const addWrappers = (method: MethodCallback): MethodCallback => { const addWrappers = (method: MethodCallback): MethodCallback => {
return selfCallWrapper(withContractCallLogger(method)); return selfCallWrapper(method);
}; };
export const cwebMain = () => { const createModule = (): ContractHandlers => {
const module: ContractHandlers = { handlers: {} }; const module: ContractHandlers = { handlers: {} };
addMethodHandler(module, PUBLIC_METHODS.CREATE_ORDER, addWrappers(createOrderPublic)); addMethodHandler(module, PUBLIC_METHODS.ADD_WORD, addWrappers(executor(addWord)));
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, SELF_REGISTER_HANDLER_NAME, selfRegisterHandler); addMethodHandler(module, SELF_REGISTER_HANDLER_NAME, selfRegisterHandler);
queue.applyQueue(module, [ queue.applyQueue(module as CKContractHandlers, [PUBLIC_METHODS.ADD_WORD]);
PUBLIC_METHODS.CANCEL_ORDER,
PUBLIC_METHODS.CHANGE_CONTRACT_OWNER,
PUBLIC_METHODS.CREATE_ORDER,
]);
executeHandler(module); return module;
};
export const cwebMain = async () => {
const module = createModule();
await executeHandler(module);
}; };

View File

@ -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<InstanceParameters, 'l1_type' | 'l1_contract_address' | 'l1_transfer_event_signature'>;
export type InstanceParametersForBtc = {
l1_type: L1Types.Btc;
} & Omit<InstanceParameters, 'l1_type' | 'l1_contract_address' | 'l1_transfer_event_signature'>;
export type OwnerClaimBody = {
owner: User;
updatedAt: number;
};

View File

@ -18,8 +18,8 @@
], ],
"exports": { "exports": {
".": { ".": {
"types": "./dist/onchain/index.d.ts", "types": "./dist/index.d.ts",
"import": "./dist/onchain/index.js" "import": "./dist/index.js"
} }
} }
} }

View File

@ -0,0 +1,3 @@
export * from './types';
export * from './methods';
export * from './wrappers';

View File

@ -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<void> {
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);
}

View File

@ -0,0 +1,3 @@
declare namespace os {
function readdir(path: string): [any[], number];
}

View File

@ -0,0 +1,3 @@
declare namespace std {
function open(path: string, mode: string): any;
}

View File

@ -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';
}

View File

@ -0,0 +1,7 @@
import { Context, NewTx } from '@coinweb/contract-kit';
export type MethodCallback = (context: Context) => Promise<NewTx[]> | NewTx[];
export type ContractHandlers = {
handlers: { [key: string]: MethodCallback };
};

View File

@ -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);
};
}

View File

@ -0,0 +1,3 @@
export * from './contract-kit';
export * from './onchain';
export * from './types';

View File

@ -2,28 +2,26 @@ import { constructContractIssuer, Context, extractUser, getAuthenticated, getCon
let rawContext: Context | null = null; let rawContext: Context | null = null;
export const setContext = (ctx: Context) => { export const setRawContext = (ctx: Context) => {
rawContext = ctx; rawContext = ctx;
}; };
export const getContext = () => rawContext; export const getRawContext = () => {
const useContext = <T>(cb: (ctx: Context) => T) => {
if (!rawContext) { if (!rawContext) {
throw new Error('Context not set'); throw new Error('Context not set');
} }
return cb(rawContext); return rawContext;
}; };
export const context = { export const context = {
get issuer() { get issuer() {
return useContext((ctx) => constructContractIssuer(getContractId(ctx.tx))); return constructContractIssuer(getContractId(getRawContext().tx));
}, },
get authenticated() { get authenticated() {
return useContext((ctx) => getAuthenticated(ctx.tx)); return getAuthenticated(getRawContext().tx);
}, },
get user() { get user() {
return useContext((ctx) => extractUser(getAuthenticated(ctx.tx))); return extractUser(getAuthenticated(getRawContext().tx));
}, },
}; };

View File

@ -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<void>) =>
async (ctx: Context): Promise<NewTx[]> => {
setRawContext(ctx);
const { args, methodName, ops } = handleState();
const execution = new Promise<void>((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();
};

View File

@ -0,0 +1,3 @@
export * from './context';
export * from './executor';
export * from './ops';

View File

@ -2,7 +2,7 @@ import { Claim, ClaimKey, constructRead, extractRead, isResolvedRead } from '@co
import { TypedClaim } from '../../types'; import { TypedClaim } from '../../types';
import { context } from '../context'; import { context } from '../context';
import { signal } from '../signal'; import { abort } from '../executor';
import { pushAwaitedOp } from './awaited'; import { pushAwaitedOp } from './awaited';
import { shiftResolvedOp } from './resolved'; import { shiftResolvedOp } from './resolved';
@ -16,12 +16,12 @@ export const readOp = <TClaim extends Claim = TypedClaim>(key: ClaimKey) => {
throw new Error('Read operation not found'); throw new Error('Read operation not found');
} }
return new Promise<TClaim | null>((resolve, reject) => { return new Promise<TClaim | null>((resolve) => {
if (isOp) { if (isOp) {
const claim = op && ((extractRead(op)?.[0] ?? null) as TClaim | null); const claim = op && ((extractRead(op)?.[0] ?? null) as TClaim | null);
resolve(claim); resolve(claim);
} else { } else {
reject(signal); abort();
} }
}); });
}; };

View File

@ -1,7 +1,7 @@
import { Claim, constructStore, isResolvedStore } from '@coinweb/contract-kit'; 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 { pushAwaitedOp } from './awaited';
import { shiftResolvedOp } from './resolved'; import { shiftResolvedOp } from './resolved';
@ -21,11 +21,11 @@ export const storeOp = (claim: Claim) => {
throw new Error('Wrong store operation'); throw new Error('Wrong store operation');
} }
return new Promise<Claim>((resolve, reject) => { return new Promise<Claim>((resolve) => {
if (isOp) { if (isOp) {
resolve(result); resolve(result);
} else { } else {
reject(signal); abort();
} }
}); });
}; };

View File

@ -1,7 +1,7 @@
import { constructTake, extractTake, isResolvedTake, Claim, ClaimKey } from '@coinweb/contract-kit'; import { constructTake, extractTake, isResolvedTake, Claim, ClaimKey } from '@coinweb/contract-kit';
import { TypedClaim } from '../../types'; import { TypedClaim } from '../../types';
import { signal } from '../signal'; import { abort } from '../executor';
import { pushAwaitedOp } from './awaited'; import { pushAwaitedOp } from './awaited';
import { shiftResolvedOp } from './resolved'; import { shiftResolvedOp } from './resolved';
@ -15,12 +15,12 @@ export const takeOp = <TClaim extends Claim = TypedClaim>(key: ClaimKey) => {
throw new Error('Take operation not found'); throw new Error('Take operation not found');
} }
return new Promise<TClaim | null>((resolve, reject) => { return new Promise<TClaim | null>((resolve) => {
if (isOp) { if (isOp) {
const claim = op && extractTake(op); const claim = op && extractTake(op);
resolve(claim as TClaim | null); resolve(claim as TClaim | null);
} else { } else {
reject(signal); abort();
} }
}); });
}; };

View File

@ -1 +0,0 @@
export const signal = Symbol('signal');

View File

@ -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 = ( import { toHex } from '../../../shared';
contractId: string,
methodName: string, export const constructCall = ({
methodArgs: unknown[], contractId,
cost: bigint, methodName,
auth: boolean = true methodArgs,
): ContractCall => { cost,
auth = true,
withQueue = true,
}: {
contractId: string;
methodName: string;
methodArgs: unknown[];
cost: bigint;
auth?: boolean;
withQueue?: boolean;
}): ContractCall => {
const issuer = constructContractIssuer(contractId); 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); return prepareQueueContractCall(issuer, { methodName, methodArgs }, cost, auth);
}; };

View File

@ -10,7 +10,6 @@ import {
QueueInput, QueueInput,
} from '@coinweb/contract-kit'; } from '@coinweb/contract-kit';
import { DataUnverified } from '@coinweb/contract-kit/dist/types/operations/data'; import { DataUnverified } from '@coinweb/contract-kit/dist/types/operations/data';
import { HexBigInt, toHex32 } from 'cwap-cm-lib/shared';
import { import {
checkIsNewHistoryAccessId, checkIsNewHistoryAccessId,
@ -21,6 +20,7 @@ import {
validateMetadata, validateMetadata,
} from '../../module/history/onchain'; } from '../../module/history/onchain';
import { ContractCallMetadataInterface, HISTORY_CODES, HistoryItem } from '../../module/history/shared'; import { ContractCallMetadataInterface, HISTORY_CODES, HistoryItem } from '../../module/history/shared';
import { HexBigInt, toHex32 } from '../../shared';
import { TX_STATUS_ERROR } from '../constants'; import { TX_STATUS_ERROR } from '../constants';
import { Exception } from '../features/exception'; import { Exception } from '../features/exception';
import { queue, queueUnlockFee } from '../features/queue'; import { queue, queueUnlockFee } from '../features/queue';

View File

@ -16,8 +16,8 @@ import {
MethodCallback, MethodCallback,
ResolvedOperation, ResolvedOperation,
} from '@coinweb/contract-kit'; } from '@coinweb/contract-kit';
import { toHex32 } from 'cwap-cm-lib/shared';
import { toHex32 } from '../../shared';
import { Logs } from '../constants'; import { Logs } from '../constants';
const normalizeData = (data: unknown) => { const normalizeData = (data: unknown) => {

View File

@ -1,5 +1,5 @@
import { Queue } from '@coinweb/contract-kit'; 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; export const queueUnlockFee = 3000n;

View File

@ -1,2 +1,4 @@
VITE_API_URL='https://api-cloud.coinweb.io/wallet' VITE_API_URL='https://api-cloud.coinweb.io/wallet'
VITE_EXPLORER_URL='https://explorer.coinweb.io' VITE_EXPLORER_URL='https://explorer.coinweb.io'
VITE_CONTRACT_ADDRESS="0xc8ed7479667e1b1a9223db34e6a91b3970c7f82686ed0712fce89117a10b77ec"

View File

@ -17,6 +17,7 @@
"@coinweb/webapp-library": "0.1.6", "@coinweb/webapp-library": "0.1.6",
"axios": "^1.7.2", "axios": "^1.7.2",
"contract.cm": "workspaces:*", "contract.cm": "workspaces:*",
"qrcode.react": "^4.2.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",

View File

@ -1,7 +1,56 @@
import { useState } from 'react';
import { constructAddWordUiCommand } from 'contract.cm';
import { QRCodeSVG } from 'qrcode.react';
export const App = () => { export const App = () => {
const [word, setWord] = useState('');
const [qrCode, setQrCode] = useState('');
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setQrCode(constructAddWordUiCommand({ word, contractId: import.meta.env.VITE_CONTRACT_ADDRESS ?? '' }));
};
return ( return (
<div> <div>
<h1>Hello World</h1> <form onSubmit={onSubmit}>
<div className="mx-auto my-10 flex max-w-2xl flex-col gap-2">
<label htmlFor="first_name" className="mb-2 block text-sm font-medium text-gray-900 dark:text-white">
Word
</label>
<input
type="text"
id="first_name"
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
placeholder="Happiness"
required
value={word}
onChange={(e) => setWord(e.target.value)}
/>
<button
type="submit"
className="w-full self-end rounded-lg bg-blue-700 px-5 py-2.5 text-center text-sm font-medium text-white hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300 sm:w-auto dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Submit
</button>
</div>
</form>
<div className="flex w-full justify-center py-4">
<QRCodeSVG
onClick={() => {
navigator.clipboard.writeText(qrCode).catch((err) => {
console.error(err);
});
}}
value={qrCode}
size={320}
level="L"
className="hover:cursor-pointer"
bgColor="transparent"
fgColor="black"
/>
</div>
</div> </div>
); );
}; };

View File

@ -4,24 +4,11 @@ import path from 'node:path';
import YAML from 'yaml'; import YAML from 'yaml';
const BaseContractTemplate = 'dex-app.cm';
const MarketMakerContractTemplate = 'market-maker.cm';
const ParamsPatternMatching = [ 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'], // ['VITE_L1_CONTRACT_ADDRESS', 'l1_contract_address'],
]; ];
const ContractIdPattern = 'VITE_L2_CONTRACT_ADDRESS'; const ContractIdPattern = 'VITE_CONTRACT_ADDRESS';
const TemplatePatternMatching = [
['_BASE_', BaseContractTemplate],
['_MAKER_', MarketMakerContractTemplate],
];
const Variants = [['eth'], ['bnb'], ['btc'], ['trx'], ['eth', 'usdt'], ['bnb', 'usdt'], ['trx', 'usdt']];
const updated = []; const updated = [];
@ -51,27 +38,10 @@ function updateValue(line, value) {
} }
function updateLine(line, parameters, index) { 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)); const paramPattern = ParamsPatternMatching.find(([pattern]) => isMatch(line, pattern));
if (paramPattern) { if (paramPattern) {
const instanceParameters = parameters.find( const value = Object.entries(parameters.parameters.content).find(([name]) => isMatch(name, paramPattern[1]))?.[1];
({ alias, template }) =>
isMatch(template, templatePattern[1]) && variant.every((pattern) => isMatch(alias, pattern))
);
if (instanceParameters) {
const value = Object.entries(instanceParameters.parameters.content).find(([name]) =>
isMatch(name, paramPattern[1])
)?.[1];
if (value !== undefined) { if (value !== undefined) {
const updatedLine = updateValue(line, value); const updatedLine = updateValue(line, value);
@ -83,16 +53,13 @@ function updateLine(line, parameters, index) {
return updatedLine; return updatedLine;
} }
} }
}
if (isMatch(line, ContractIdPattern)) { if (isMatch(line, ContractIdPattern)) {
const templates = Object.entries(index).filter(([name]) => isMatch(name, templatePattern[1])); const instances = Object.entries(index).find((template) => template[1].target_instances.length)?.[1]
const instances = templates.find((template) => template[1].target_instances.length)?.[1].target_instances; .target_instances;
if (instances?.length) { if (instances?.length) {
const contractId = instances.find(({ alias }) => const contractId = instances[0]?.instance_id;
variant.every((pattern) => isMatch(alias, pattern))
)?.instance_id;
if (contractId !== undefined) { if (contractId !== undefined) {
const updatedLine = updateValue(line, '0x' + contractId); const updatedLine = updateValue(line, '0x' + contractId);
@ -151,7 +118,7 @@ function printResults() {
throw new Error('Cannot find config for profile', profile); 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'); const env = fs.readFileSync(envFilePath, 'utf-8');
if (!env) { if (!env) {

View File

@ -6970,6 +6970,15 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "qs@npm:^6.12.3":
version: 6.14.0 version: 6.14.0
resolution: "qs@npm:6.14.0" resolution: "qs@npm:6.14.0"
@ -8702,6 +8711,7 @@ __metadata:
jsdom: "npm:^24.0.0" jsdom: "npm:^24.0.0"
postcss: "npm:^8.4.39" postcss: "npm:^8.4.39"
postcss-cli: "npm:^11.0.0" postcss-cli: "npm:^11.0.0"
qrcode.react: "npm:^4.2.0"
react: "npm:^18.2.0" react: "npm:^18.2.0"
react-dom: "npm:^18.2.0" react-dom: "npm:^18.2.0"
react-router-dom: "npm:^6.22.3" react-router-dom: "npm:^6.22.3"