feat: add packages with initial structure

This commit is contained in:
Alex 2025-07-29 13:17:52 +03:00
parent e3c9659af8
commit c5237698cd
130 changed files with 1600 additions and 64 deletions

View File

@ -19,8 +19,6 @@ use:
- jump-forwarder.cm v0.1.5
- jump_forwarder_devnet v0.1.5
interpreters: {}
contract_templates:
@ -33,4 +31,3 @@ contract_instances:
template: contract.cm v0.0.1
parameters:
content: []

View File

@ -10,7 +10,7 @@
"build:production": "yarn build:cm && yarn workspace dapp-ui build:production",
"build:lib": "yarn workspaces foreach -Ap --include 'packages/lib' --include 'packages/cwait' run build",
"build:cm": "yarn build:lib && yarn workspaces foreach -Ap --include 'packages/*.cm' run build",
"build:non-cm": "yarn workspaces foreach -Ap --include 'packages/*' --exclude 'packages/*.cm' --exclude 'packages/lib' --exclude 'packages/cwait' run build",
"build:non-cm": "yarn workspaces foreach -Ap --include 'packages/*' --exclude 'packages/*.cm' --exclude 'packages/*.ignore' --exclude 'packages/lib' --exclude 'packages/cwait' run build",
"create-index": "cweb-tool create-index -c ./.cweb-config/config.yaml --profile $REGISTRATION_PROFILE",
"publish-index": "cweb-tool publish-index -c ./.cweb-config/config.yaml --profile $REGISTRATION_PROFILE",
"deploy-contracts": "yarn build:cm && yarn publish-actions",
@ -39,7 +39,7 @@
"g:lint": "cd $INIT_CWD && eslint",
"g:vitest": "cd $INIT_CWD && vitest",
"g:lint-staged": "cd $INIT_CWD && lint-staged",
"publish:lib": "yarn workspaces foreach -Ap --include 'packages/cwap-cm-*' run publish:lib"
"publish:lib": "yarn workspaces foreach -Ap --include 'packages/lib' run publish:lib"
},
"resolutions": {
"@coinweb/cweb-tool": "2.0.22",

View File

@ -0,0 +1,37 @@
{
"name": "btc-like-provider.cm",
"version": "0.0.1-dev",
"type": "module",
"scripts": {
"build": "yarn build:files && yarn pack:all",
"build:files": "yarn clean && yarn g:tsc -p tsconfig.build.json && NODE_ENV=production ./scripts/build.sh",
"start": "node src/offchain/index.js",
"test": "yarn g:vitest run --pool=forks",
"test:prepare": "mkdir -p tests_data && cp ../../.cweb_tool/simulation/unknown.json tests_data/state.json && cp ../../.dapp-ecosystem-lock-test.yaml tests_data/index.yaml",
"test:watch": "yarn g:vitest",
"test:coverage": "yarn g:vitest run --coverage",
"pack:relink": "cd $INIT_CWD && node scripts/package-relink.js",
"pack:all": "yarn pack:cweb && yarn pack:relink && yarn pack:yarn && yarn pack:relink",
"pack:cweb": "yarn g:cweb-tool pack . dist/out ../../.cweb-config/cweb-pack.yaml -c ../../.cweb-config/config.yaml --profile $REGISTRATION_PROFILE",
"pack:yarn": "cd dist/out && touch yarn.lock && yarn pack -o my-contract-module.tgz",
"clean": "rm -rf cweb_dist dist .calls.yaml",
"typecheck": "yarn g:tsc --noEmit",
"dev": "yarn g:tsc -p tsconfig.dev.json --watch",
"lint": "yarn g:lint ."
},
"dependencies": {
"@coinweb/contract-kit": "0.2.6",
"@coinweb/self-register": "0.1.3",
"@noble/hashes": "1.4.0",
"bech32": "2.0.0",
"bs58": "6.0.0",
"lib": "workspaces:*"
},
"devDependencies": {
"esbuild": "^0.20.2"
},
"main": "cweb_dist/offchain/index.js",
"engines": {
"cweb_interpreter": "a344c6003922f9e44385f6e8234a7d2567d9a676b14330ad3b42cbd1948a92bf"
}
}

View File

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

View File

@ -1,6 +1,6 @@
import { constructCall, constructUiCommand } from 'lib/offchain';
import { AddWordArgs, FEE, PUBLIC_METHODS } from './shared';
import { AddWordArgs, FEE, PUBLIC_METHODS } from '../shared';
export const constructAddWordUiCommand = ({ word, contractId }: { word: string; contractId: string }) => {
return constructUiCommand([

View File

@ -1,6 +1,6 @@
import { constructCwebMain } from 'cwait';
import { PUBLIC_METHODS } from '../offchain/shared';
import { PUBLIC_METHODS } from '../shared';
import { addWord } from './addWord';

View File

@ -0,0 +1,96 @@
export enum Key {
ACTIVE_INDEX = 'ACTIVE_INDEX',
MAKER_INDEX = 'MAKER_INDEX',
DATE_INDEX = 'DATE_INDEX',
RATE_INDEX = 'RATE_INDEX',
MAKER = 'MAKER',
ORDER = 'ORDER', //ORDER_STATE + ORDER_FUNDS
LATEST_ORDER_ID = 'LATEST_ORDER_ID', //INCREMENT_INDEX
ORDER_DIRECTION_INDEX = 'ORDER_DIRECTION_INDEX',
PACT = 'PACT', //ORDER_STATE + ORDER_COLLATERAL
LATEST_PACT_ID = 'LATEST_PACT_ID', //INCREMENT_INDEX
PACT_PENALTY_INDEX = 'PACT_PENALTY_INDEX',
SWAP_INFO = 'SWAP_INFO', //L1_TX_INFO
PAYMENT_NONCE = 'PAYMENT_NONCE', //BTC_NONCE
UNIQUE_VALUE = 'UNIQUE_VALUE',
FEE_POOL_ADDRESS = 'FEE_POOL_ADDRESS',
}
export enum PUBLIC_METHODS {
CREATE_ORDERS = 'CREATE_ORDERS',
CANCEL_ORDERS = 'CANCEL_ORDERS',
DEPOSIT_COLLATERAL = 'DEPOSIT_COLLATERAL',
WITHDRAW_COLLATERAL = 'WITHDRAW_COLLATERAL',
}
export const FEE: Record<`${PUBLIC_METHODS}` | 'CHANGE_FEE_POOL_ADDRESS', bigint> = {
CREATE_ORDERS: 10000000n,
CANCEL_ORDERS: 10000000n,
DEPOSIT_COLLATERAL: 10000000n,
WITHDRAW_COLLATERAL: 10000000n,
CHANGE_FEE_POOL_ADDRESS: 10000000n,
};
export enum ORDER_STATUS {
ACTIVE = 'ACTIVE',
DEACTIVATED = 'DEACTIVATED',
DISABLED = 'DISABLED',
}
export enum PACT_STATUS {
ACTIVE = 'ACTIVE',
DISABLED = 'DISABLED',
}
export enum AddressEncoding {
Hex = 'Hex',
Base58 = 'Base58',
Bech32 = 'Bech32',
Bech32m = 'Bech32m',
}
export enum ProviderType {
Evm = 'Evm',
Btc = 'Btc',
}
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 = 'btc',
tbtc = 'tbtc',
}
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

@ -0,0 +1,3 @@
export * from './keys';
export * from './constants/constants';
export * from './types';

View File

@ -0,0 +1,121 @@
import type { HexString, User } from '@coinweb/contract-kit';
import { HexBigInt } from 'lib/shared';
import { ORDER_STATUS, OrderBatchFields, AddressEncoding } from './constants/constants';
export type ProviderData = unknown;
export type BtcProviderData = {
l1TxId: string;
vout: number;
psbt: string;
};
export type DirectionParameter = {
provider: string;
providerData: ProviderData;
value: HexBigInt;
};
export type Direction = {
from: DirectionParameter;
to: DirectionParameter;
};
export type OrderStateClaimBody = {
recipient: string;
cwebAmount: HexBigInt;
minCwebSwapAmount: HexBigInt;
directions: Direction[];
createdAt: number;
expirationDate: number;
status: ORDER_STATUS;
parentTxId: string;
history: HistoryAccess;
maker: User;
};
export type SwapInfoData = {
l1TxId: string;
recipientAddress: string;
backPayAddress: string;
amount: HexBigInt;
l2TxId: string;
};
export type SwapToBtcL1Payload = {
minL1Amount: HexBigInt;
promisedL1Amount: HexBigInt;
l1Recipient: HexString;
fallbackRecipient: HexString;
};
export type SwapToEvmL1Payload = {
minL1Amount: HexBigInt;
promisedL1Amount: HexBigInt;
l1Recipient: HexString;
};
export type SwapToEvmPreset = {
toL1ChainType: L1Types.Evm;
c2ContractId: HexString;
c2ContractMethod: HexString;
fallbackC1ContractId: HexString;
fallbackC1ContractMethod: HexString;
};
export type SwapToBtcPreset = {
toL1ChainType: L1Types.Btc;
c2ContractId: HexString;
c2ContractMethod: HexString;
encoding: AddressEncoding;
};
export type SwapPreset = SwapToEvmPreset | SwapToBtcPreset;
export type CreateOrderBaseParams = {
cwebAmount?: bigint;
minCwebSwapAmount?: bigint;
directions: Direction[];
};
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 = {
bech32: string;
scriptHash: number;
};

View File

@ -1,13 +0,0 @@
export enum Key {
WORD = 'WORD',
SET = 'SET',
BY_LETTER = 'BY_LETTER',
}
export enum PUBLIC_METHODS {
ADD_WORD = 'ADD_WORD',
}
export const FEE = {
ADD_WORD: 10000000n,
};

View File

@ -1,29 +0,0 @@
import type { ClaimKey } from '@coinweb/contract-kit';
import { Key } from './constants';
/* FirstPart */
export const createWordFirstPart = () => [Key.WORD];
export const createByLetterFirstPart = (letter: string) => [Key.BY_LETTER, letter];
export const createSetFirstPart = () => [Key.SET];
/* Key */
export const createWordKey = (id: string) =>
({
first_part: createWordFirstPart(),
second_part: [id],
}) satisfies ClaimKey;
export const createByLetterKey = (letter: string, id: string) =>
({
first_part: createByLetterFirstPart(letter),
second_part: [id],
}) satisfies ClaimKey;
export const createSetKey = (letter: string) =>
({
first_part: createSetFirstPart(),
second_part: [letter],
}) satisfies ClaimKey;

View File

@ -1,9 +0,0 @@
export type SetClaimBody = {
set: string;
};
export type WordClaimBody = {
word: string;
};
export type AddWordArgs = [string];

View File

@ -0,0 +1 @@
deploy/calls.yaml

View File

@ -0,0 +1,12 @@
# this is the contract id
__CWEB_CONTRACT_SELF_REFERENCE__:
# out contract does not take any arguments, so we pass an empty object
args:
- "CREATE_ORDER"
- "0x000000000000000000000000000000000000000000000001236efcbcbb340000"
- "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
- "0x9A4250b8c30Ca3cC5Dc33Ddbe30f71509a2E60a5"
- "0x00000000000000000000000000000000000000000000000002ea11e32ad50000"
- "0x00000000000000000000000000000000000000000000000000000000000186a0"
cweb_provided: "0x00000000000000000000000000000000000000000000000126590e9fe60a86a0"
auth: true

View File

@ -0,0 +1,37 @@
{
"name": "evm-provider.cm",
"version": "0.0.1-dev",
"type": "module",
"scripts": {
"build": "yarn build:files && yarn pack:all",
"build:files": "yarn clean && yarn g:tsc -p tsconfig.build.json && NODE_ENV=production ./scripts/build.sh",
"start": "node src/offchain/index.js",
"test": "yarn g:vitest run --pool=forks",
"test:prepare": "mkdir -p tests_data && cp ../../.cweb_tool/simulation/unknown.json tests_data/state.json && cp ../../.dapp-ecosystem-lock-test.yaml tests_data/index.yaml",
"test:watch": "yarn g:vitest",
"test:coverage": "yarn g:vitest run --coverage",
"pack:relink": "cd $INIT_CWD && node scripts/package-relink.js",
"pack:all": "yarn pack:cweb && yarn pack:relink && yarn pack:yarn && yarn pack:relink",
"pack:cweb": "yarn g:cweb-tool pack . dist/out ../../.cweb-config/cweb-pack.yaml -c ../../.cweb-config/config.yaml --profile $REGISTRATION_PROFILE",
"pack:yarn": "cd dist/out && touch yarn.lock && yarn pack -o my-contract-module.tgz",
"clean": "rm -rf cweb_dist dist .calls.yaml",
"typecheck": "yarn g:tsc --noEmit",
"dev": "yarn g:tsc -p tsconfig.dev.json --watch",
"lint": "yarn g:lint ."
},
"dependencies": {
"@coinweb/contract-kit": "0.2.6",
"@coinweb/self-register": "0.1.3",
"@noble/hashes": "1.4.0",
"bech32": "2.0.0",
"bs58": "6.0.0",
"lib": "workspaces:*"
},
"devDependencies": {
"esbuild": "^0.20.2"
},
"main": "cweb_dist/offchain/index.js",
"engines": {
"cweb_interpreter": "a344c6003922f9e44385f6e8234a7d2567d9a676b14330ad3b42cbd1948a92bf"
}
}

View File

@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
ROOT=$DIR/..
rm -rf $ROOT/{cweb_dist/{on,off}chain,dist/tmp/{step{1,2,3},final}}
mkdir -p $ROOT/{cweb_dist/{on,off}chain,dist/tmp/{step{1,2,3},final}}
(
cd $ROOT
cp -rf dist/offchain/. cweb_dist/offchain/
## Bundle and transpile `index.js` so it can be understood by the quickjs interpreter:
echo 'import {cwebMain as f} from "../../../dist/onchain/index"; f();' \
> dist/tmp/step1/onchain.js
yarn esbuild \
--bundle \
--log-level=error \
--format=esm \
dist/tmp/step1/onchain.js \
--outfile=dist/tmp/step2/onchain.js
echo 'import * as std from "std";' |
cat - dist/tmp/step2/onchain.js \
> dist/tmp/step3/onchain.js
yarn esbuild \
--bundle \
--log-level=error \
--format=esm \
--external:std \
--tree-shaking=true \
dist/tmp/step3/onchain.js \
--outfile=dist/tmp/final/onchain.js
cp dist/tmp/final/onchain.js cweb_dist/onchain/index.js
)
rm -rf $ROOT/dist/{tmp,offchain,onchain}

View File

@ -0,0 +1,33 @@
import { readFileSync, writeFileSync } from 'fs';
import { resolve } from 'path';
import { fileURLToPath } from 'url';
const workspaceLink = 'workspaces:*';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
// Update target package.json
const targetPath = resolve(__dirname, '../dist/out/package.json');
const targetPackage = JSON.parse(readFileSync(targetPath, 'utf8'));
// Process all cm- dependencies
Object.keys(targetPackage.dependencies).forEach((dependency) => {
if (dependency.startsWith('cwap-cm-')) {
// Read source version from the corresponding package
const sourcePath = resolve(__dirname, `../../${dependency}/package.json`);
try {
const sourcePackage = JSON.parse(readFileSync(sourcePath, 'utf8'));
const sourceVersion = sourcePackage.version;
if (targetPackage.dependencies[dependency] === workspaceLink) {
targetPackage.dependencies[dependency] = sourceVersion;
} else {
targetPackage.dependencies[dependency] = workspaceLink;
}
} catch (err) {
console.warn(`Warning: Could not read package.json for ${dependency} at ${sourcePath}: ${err.message}`);
}
}
});
// Write back the updated package.json
writeFileSync(targetPath, JSON.stringify(targetPackage, null, 2));

View File

@ -0,0 +1,41 @@
import { ClaimKey } from '@coinweb/contract-kit';
import { Client } from 'lib/offchain';
import { SetClaimBody, WordClaimBody } from './shared';
import { createWordFirstPart, createSetKey, createWordKey, createByLetterFirstPart } from './shared/keys';
export const getWord = async (client: Client, id: string) => {
const key = createWordKey(id);
const claimResponse = (await client.fetchClaims(key.first_part, key.second_part))[0];
const data = claimResponse.content.body as WordClaimBody;
return data.word;
};
export const getWords = async (client: Client): Promise<string[]> => {
const claimsResponse = await client.fetchClaims(createWordFirstPart(), null);
return claimsResponse.map(({ content }) => (content.body as WordClaimBody).word);
};
export const getWordsByLetter = async (client: Client, letter: string): Promise<string[]> => {
const indexResponse = await client.fetchClaims(createByLetterFirstPart(letter), null);
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

@ -0,0 +1,3 @@
export * from './api';
export * from './uiCommands';
export * from '../shared';

View File

@ -0,0 +1,15 @@
import { constructCall, constructUiCommand } from 'lib/offchain';
import { AddWordArgs, FEE, PUBLIC_METHODS } from '../shared';
export const constructAddWordUiCommand = ({ word, contractId }: { word: string; contractId: string }) => {
return constructUiCommand([
constructCall({
contractId,
methodName: PUBLIC_METHODS.ADD_WORD,
methodArgs: [[word] satisfies AddWordArgs],
cost: FEE.ADD_WORD,
withQueue: false,
}),
]);
};

View File

@ -0,0 +1,94 @@
import path from 'node:path';
import { constructContractIssuer, constructContractRef, type PreparedCallInfo } from '@coinweb/contract-kit';
import { UnitTest, type ExecInfo, type RoundInfo, type UnitTestContext, type DbWriteOp } from '@coinweb/testing-sdk';
import { describe, it, expect, beforeAll } from 'vitest';
import { AddWordArgs, FEE, PUBLIC_METHODS } from '../../offchain/shared';
import { CONTRACT_INSTANCE_ID, TEST_PRIVATE_KEY, TEST_PUBLIC_KEY, waitRoundsCount } from './helpers';
const addWordRound = (creatorAccount: Buffer): RoundInfo => {
const self = constructContractIssuer(CONTRACT_INSTANCE_ID);
const kvDeleteCallInfo: PreparedCallInfo = {
ref: constructContractRef(self, []),
methodInfo: { methodName: PUBLIC_METHODS.ADD_WORD, methodArgs: ['TEST_HELLO'] satisfies AddWordArgs },
contractArgs: [],
contractInfo: { providedCweb: FEE.ADD_WORD, authenticated: null }, // null = authenticated account in tests
};
const withFunds = { type: { privateKey: creatorAccount } };
return {
txsInfo: {
// @ts-ignore
txs: [{ callInfo: kvDeleteCallInfo, withFunds }],
l1_events: [],
},
claims: [],
blocks_on: [],
};
};
const runTest = async (context: UnitTestContext, input: ExecInfo, checkFn: (results: DbWriteOp[]) => void) => {
const test = new UnitTest(context);
await test.load(path.join(import.meta.dirname, '../../../tests_data/state.json'));
await test.run(input, { checkFn });
};
describe('PUBLIC_METHODS.ADD_WORD Tests', () => {
let creatorAccount: { publicKey: Buffer; privateKey: Buffer };
beforeAll(() => {
creatorAccount = {
privateKey: Buffer.from(TEST_PRIVATE_KEY, 'hex'),
publicKey: Buffer.from(TEST_PUBLIC_KEY, 'hex'),
};
});
it('Should successfully execute', async () => {
const input: ExecInfo = {
rounds: [...waitRoundsCount(50), addWordRound(creatorAccount.privateKey), ...waitRoundsCount(2)],
};
const checkFn = (results: DbWriteOp[]) => {
expect(results).toBeDefined();
// @ts-ignore
// const errors = findAllValuesRecursivelyByKey<{ ContractError: ContractError }>(results, 'Invalid');
// expect(errors.length).toBe(0);
// @ts-ignore
// const storeOps = findAllValuesRecursivelyByKey<CwebStore>(results, 'StoreOp');
// expect(storeOps.length).toBeGreaterThan(0);
// const existingPayload = storeOps.find((s) => isEqual(s.key.first_part, createFirstPartDefault()));
// expect(existingPayload).toBeDefined();
// expect(existingPayload?.key).toStrictEqual(createDefaultKey(Number.MAX_SAFE_INTEGER));
// expect(existingPayload?.key.first_part).toStrictEqual(createFirstPartDefault());
// const { id, data, owner, tx } = existingPayload?.body as StoreKvPayload;
// expect(id).toBeDefined();
// expect(data).toBeDefined();
// expect(owner).toBeDefined();
// expect(tx).toBeDefined();
// expect(tx?.timestamp).toBeDefined();
// expect(tx?.coinwebTxId).toBeDefined();
// expect(Number(id)).toStrictEqual(Number.MAX_SAFE_INTEGER);
// expect(owner).toStrictEqual({
// auth: 'EcdsaContract',
// payload: TEST_PUBLIC_KEY,
// });
};
const context: UnitTestContext = {
name: 'add_word',
testPath: path.join(import.meta.dirname, './tests_data/add_word'),
verbose: true,
};
await runTest(context, input, checkFn);
});
});

View File

@ -0,0 +1,35 @@
import path from 'node:path';
import { getInstanceFromIndex, type RoundInfo } from '@coinweb/testing-sdk';
const onchainPackage = (await import('../../../dist/out/package.json')) as {
name: string;
};
export const CONTRACT_INSTANCE = await getInstanceFromIndex({
path: path.resolve(import.meta.dirname, '../../../tests_data/index.yaml'),
instance_alias: 'cwait-contract 0.0.1-test',
});
export const CONTRACT_INSTANCE_ID = '0x'.concat(CONTRACT_INSTANCE.instance_id.replace(/0x/, ''));
export const CONTRACT_TEMPLATE_ID = `0x${onchainPackage.name.substring(5)}`;
export const TEST_PRIVATE_KEY = '31c70848e4e3aaffcf91f134853ec966e913aa9a813115bcb81512e7625f46a9';
export const TEST_PUBLIC_KEY = '03951f89fe78e13f295d96eb7afa1e0da726df7d58f9c84f7144e5febc30efeec4';
export const waitRoundsCount = (roundsCount: number): RoundInfo[] => {
const emptyRound: RoundInfo = {
txsInfo: {
txs: [],
l1_events: [],
},
claims: [],
blocks_on: [],
};
const rounds: RoundInfo[] = [];
for (let i = 0; i < roundsCount; i++) {
rounds.push(emptyRound);
}
return rounds;
};

View File

@ -0,0 +1,54 @@
import { constructClaim } from '@coinweb/contract-kit';
import { readOp, storeOp, cwait } from 'cwait';
import { TypedClaim } from '../../../lib/dist/shared/types';
import { AddWordArgs, createWordKey, WordClaimBody } from '../offchain/shared';
import { extraLogic } from './extraLogic';
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 < 0 ? hash * -1 : hash).toString(16)}`;
}
export const addWord = cwait(async (...[word]: AddWordArgs) => {
console.log('addWord START');
const id = hashCode(word);
console.log('await storeOp');
await storeOp(constructClaim(createWordKey(id), { word }, '0x0'));
console.log('await extraLogic');
const [wordClaim] = await Promise.all([extraLogic(id, 1), extraLogic(id, 2)]);
const newWord1 = (wordClaim?.body.word ?? '') + '_1';
const newId1 = hashCode(newWord1);
const newWord2 = (wordClaim?.body.word ?? '') + '_2';
const newId2 = hashCode(newWord2);
const newWord3 = (wordClaim?.body.word ?? '') + '_3';
const newId3 = hashCode(newWord3);
console.log('free storeOp');
storeOp(constructClaim(createWordKey(newId1), { word: wordClaim?.body.word + '_free' }, '0x0'));
console.log('await Promise.all');
await Promise.all([
storeOp(constructClaim(createWordKey(newId2), { word: wordClaim?.body.word + '_in_promise_all' }, '0x0')),
storeOp(constructClaim(createWordKey(newId3), { word: 'WIN' }, '0x0')),
]);
console.log('free readOp');
readOp<TypedClaim<WordClaimBody>>(createWordKey(newId3));
console.log('addWord END');
});

View File

@ -0,0 +1,9 @@
import { constructCwebMain } from 'cwait';
import { PUBLIC_METHODS } from '../shared';
import { addWord } from './addWord';
export const cwebMain = constructCwebMain({
[PUBLIC_METHODS.ADD_WORD]: addWord,
});

View File

@ -0,0 +1,31 @@
import { constructClaim } from '@coinweb/contract-kit';
import { cwait, lock, readOp, storeOp, TypedClaim } from 'cwait';
import { createWordKey, WordClaimBody } from '../offchain';
import { extraLogic2 } from './extraLogic2';
export const extraLogic = cwait(async (id: string, i: number) => {
console.log('extraLogic START ++ ', i);
console.log('extraLogic lock + ', i);
const unlock = await lock(createWordKey(id));
console.log('extraLogic readOp + ', i);
const result = await readOp<TypedClaim<WordClaimBody>>(createWordKey(id));
console.log('extraLogic storeOp + ', i);
await storeOp(
constructClaim(
createWordKey(id),
{ word: result?.body.word.split('').reverse().join('') + '_extraLogic + ' + i },
'0x0'
)
);
console.log('extraLogic unlock + ', i);
await unlock();
console.log('extraLogic return extraLogic2 + ', i);
return extraLogic2(id, i);
});

View File

@ -0,0 +1,13 @@
import { cwait, readOp, TypedClaim } from 'cwait';
import { createWordKey, WordClaimBody } from '../offchain';
export const extraLogic2 = cwait(async (id: string, i: number) => {
console.log('extraLogic2 START + ', i);
console.log('await extraLogic2 readOp + ', i);
const result = await readOp<TypedClaim<WordClaimBody>>(createWordKey(id));
console.log('extraLogic2 END + ', i);
return result;
});

View File

@ -0,0 +1 @@
export * from './contract';

View File

@ -0,0 +1,96 @@
export enum Key {
ACTIVE_INDEX = 'ACTIVE_INDEX',
MAKER_INDEX = 'MAKER_INDEX',
DATE_INDEX = 'DATE_INDEX',
RATE_INDEX = 'RATE_INDEX',
MAKER = 'MAKER',
ORDER = 'ORDER', //ORDER_STATE + ORDER_FUNDS
LATEST_ORDER_ID = 'LATEST_ORDER_ID', //INCREMENT_INDEX
ORDER_DIRECTION_INDEX = 'ORDER_DIRECTION_INDEX',
PACT = 'PACT', //ORDER_STATE + ORDER_COLLATERAL
LATEST_PACT_ID = 'LATEST_PACT_ID', //INCREMENT_INDEX
PACT_PENALTY_INDEX = 'PACT_PENALTY_INDEX',
SWAP_INFO = 'SWAP_INFO', //L1_TX_INFO
PAYMENT_NONCE = 'PAYMENT_NONCE', //BTC_NONCE
UNIQUE_VALUE = 'UNIQUE_VALUE',
FEE_POOL_ADDRESS = 'FEE_POOL_ADDRESS',
}
export enum PUBLIC_METHODS {
CREATE_ORDERS = 'CREATE_ORDERS',
CANCEL_ORDERS = 'CANCEL_ORDERS',
DEPOSIT_COLLATERAL = 'DEPOSIT_COLLATERAL',
WITHDRAW_COLLATERAL = 'WITHDRAW_COLLATERAL',
}
export const FEE: Record<`${PUBLIC_METHODS}` | 'CHANGE_FEE_POOL_ADDRESS', bigint> = {
CREATE_ORDERS: 10000000n,
CANCEL_ORDERS: 10000000n,
DEPOSIT_COLLATERAL: 10000000n,
WITHDRAW_COLLATERAL: 10000000n,
CHANGE_FEE_POOL_ADDRESS: 10000000n,
};
export enum ORDER_STATUS {
ACTIVE = 'ACTIVE',
DEACTIVATED = 'DEACTIVATED',
DISABLED = 'DISABLED',
}
export enum PACT_STATUS {
ACTIVE = 'ACTIVE',
DISABLED = 'DISABLED',
}
export enum AddressEncoding {
Hex = 'Hex',
Base58 = 'Base58',
Bech32 = 'Bech32',
Bech32m = 'Bech32m',
}
export enum ProviderType {
Evm = 'Evm',
Btc = 'Btc',
}
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 = 'btc',
tbtc = 'tbtc',
}
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

@ -0,0 +1,3 @@
export * from './keys';
export * from './constants/constants';
export * from './types';

View File

@ -0,0 +1,121 @@
import type { HexString, User } from '@coinweb/contract-kit';
import { HexBigInt } from 'lib/shared';
import { ORDER_STATUS, OrderBatchFields, AddressEncoding } from './constants/constants';
export type ProviderData = unknown;
export type BtcProviderData = {
l1TxId: string;
vout: number;
psbt: string;
};
export type DirectionParameter = {
provider: string;
providerData: ProviderData;
value: HexBigInt;
};
export type Direction = {
from: DirectionParameter;
to: DirectionParameter;
};
export type OrderStateClaimBody = {
recipient: string;
cwebAmount: HexBigInt;
minCwebSwapAmount: HexBigInt;
directions: Direction[];
createdAt: number;
expirationDate: number;
status: ORDER_STATUS;
parentTxId: string;
history: HistoryAccess;
maker: User;
};
export type SwapInfoData = {
l1TxId: string;
recipientAddress: string;
backPayAddress: string;
amount: HexBigInt;
l2TxId: string;
};
export type SwapToBtcL1Payload = {
minL1Amount: HexBigInt;
promisedL1Amount: HexBigInt;
l1Recipient: HexString;
fallbackRecipient: HexString;
};
export type SwapToEvmL1Payload = {
minL1Amount: HexBigInt;
promisedL1Amount: HexBigInt;
l1Recipient: HexString;
};
export type SwapToEvmPreset = {
toL1ChainType: L1Types.Evm;
c2ContractId: HexString;
c2ContractMethod: HexString;
fallbackC1ContractId: HexString;
fallbackC1ContractMethod: HexString;
};
export type SwapToBtcPreset = {
toL1ChainType: L1Types.Btc;
c2ContractId: HexString;
c2ContractMethod: HexString;
encoding: AddressEncoding;
};
export type SwapPreset = SwapToEvmPreset | SwapToBtcPreset;
export type CreateOrderBaseParams = {
cwebAmount?: bigint;
minCwebSwapAmount?: bigint;
directions: Direction[];
};
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 = {
bech32: string;
scriptHash: number;
};

View File

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["./src/**/*.ts"],
"exclude": ["**/__tests__", "**/__mocks__", "*.tests.ts"]
}

View File

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"rootDir": "./src",
"outDir": "./cweb_dist/"
},
"exclude": ["**/__tests__", "**/__mocks__", "vitest.config.ts", "tsconfig.dev.json", "*.tests.ts"]
}

View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"strict": true,
"target": "ESNEXT",
"module": "ESNEXT",
"moduleResolution": "Bundler",
"rootDir": ".",
"resolveJsonModule": true,
"esModuleInterop": true,
"types": ["vitest/globals"]
},
"include": ["src/**/*.ts", "vitest.*.ts", "__tests__", "../cwap-cm-lib/src/onchain/features/logger.ts"]
}

View File

@ -0,0 +1,14 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
reporters: 'default',
silent: false,
testTimeout: 240000,
teardownTimeout: 240000,
hookTimeout: 240000,
pool: 'forks',
},
});

View File

@ -0,0 +1,106 @@
import { constructClaimKey } from '@coinweb/contract-kit';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ClaimKeyFirstPartConstructor = (...args: any[]) => unknown[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ClaimKeySecondPartConstructor = (...args: any[]) => unknown[] | null;
type ClaimKeyConstructors = {
firstPart?: ClaimKeyFirstPartConstructor;
secondPart: ClaimKeySecondPartConstructor;
};
type ClaimKey<TFirstPart extends unknown[], TSecondPart extends unknown[] | null> = {
first_part: TFirstPart;
second_part: TSecondPart;
};
export type ClaimKeyDefinitions = {
[Key: string]: ClaimKeyConstructors | ClaimKeyDefinitions;
};
type KeyConstructor<
TCreateSecondPart extends (...args: unknown[]) => unknown[] | null,
TCreateFirstPart extends ((...args: unknown[]) => unknown[]) | undefined = undefined,
> = {
firstPart: TCreateFirstPart extends ClaimKeyFirstPartConstructor
? (...args: Parameters<TCreateFirstPart>) => [string, ...ReturnType<TCreateFirstPart>]
: () => [string];
secondPart: TCreateSecondPart;
} & (TCreateFirstPart extends ClaimKeySecondPartConstructor
? (
...args: [...Parameters<TCreateFirstPart>, ...Parameters<TCreateSecondPart>]
) => ClaimKey<[string, ...ReturnType<TCreateFirstPart>], ReturnType<TCreateSecondPart>>
: (...args: Parameters<TCreateSecondPart>) => ClaimKey<[string], ReturnType<TCreateSecondPart>>);
type ConstructKeys<T extends ClaimKeyDefinitions> = {
[Key in keyof T]: T[Key] extends ClaimKeyDefinitions
? ConstructKeys<T[Key]>
: T[Key] extends ClaimKeyConstructors
? KeyConstructor<T[Key]['secondPart'], T[Key]['firstPart']>
: never;
};
const domainPropSymbol = Symbol('domain');
export const constructClaimKeys = <T extends ClaimKeyDefinitions>(domain: string, keys: T): ConstructKeys<T> =>
new Proxy(keys as ClaimKeyDefinitions & { [domainPropSymbol]?: string }, {
get: (target, prop) => {
if (typeof prop !== 'string') {
throw new Error('Property is not a string');
}
const value = target[prop];
const currentDomain = target[domainPropSymbol] ? `${target[domainPropSymbol]}_${prop}` : domain;
if ('secondPart' in value) {
const secondPart = value.secondPart;
const firstPartDefinedConstructor = value.firstPart;
if (typeof firstPartDefinedConstructor === 'function') {
const firstPart = (...args: Parameters<typeof firstPartDefinedConstructor>) => [
currentDomain,
...firstPartDefinedConstructor(...args),
];
return {
firstPart,
secondPart,
};
}
return {
firstPart: () => [currentDomain],
secondPart,
};
}
(value as ClaimKeyDefinitions & { [domainPropSymbol]: string })[domainPropSymbol] = currentDomain;
return value;
},
apply(target, prop, argArray) {
const value = target[prop];
const currentDomain = target[domainPropSymbol] ? `${target[domainPropSymbol]}_${prop}` : domain;
const secondPartConstructor = value.secondPart;
if (typeof secondPartConstructor !== 'function') {
throw new Error('Property is not callable');
}
const secondPartArgs = argArray.slice(argArray.length - secondPartConstructor.length);
const secondPart = secondPartConstructor(...secondPartArgs);
const firstPartDefinedConstructor = value.firstPart;
const firstPart: unknown[] = [currentDomain];
if (typeof firstPartDefinedConstructor === 'function') {
firstPart.push(...firstPartDefinedConstructor(...argArray));
}
return constructClaimKey(firstPart, secondPart);
},
}) as ConstructKeys<T>;

View File

@ -5,3 +5,4 @@ export * from './stableStringifyObject';
export * from './toHex';
export * from './toHex32';
export * from './isEqualUser';
export * from './claimKeys';

1
packages/market.cm/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
deploy/calls.yaml

View File

@ -0,0 +1,12 @@
# this is the contract id
__CWEB_CONTRACT_SELF_REFERENCE__:
# out contract does not take any arguments, so we pass an empty object
args:
- "CREATE_ORDER"
- "0x000000000000000000000000000000000000000000000001236efcbcbb340000"
- "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
- "0x9A4250b8c30Ca3cC5Dc33Ddbe30f71509a2E60a5"
- "0x00000000000000000000000000000000000000000000000002ea11e32ad50000"
- "0x00000000000000000000000000000000000000000000000000000000000186a0"
cweb_provided: "0x00000000000000000000000000000000000000000000000126590e9fe60a86a0"
auth: true

View File

@ -1,6 +1,6 @@
{
"name": "contract.cm",
"version": "0.0.1",
"name": "market.cm",
"version": "0.0.1-dev",
"type": "module",
"scripts": {
"build": "yarn build:files && yarn pack:all",
@ -24,7 +24,8 @@
"@coinweb/self-register": "0.1.3",
"@noble/hashes": "1.4.0",
"bech32": "2.0.0",
"bs58": "6.0.0"
"bs58": "6.0.0",
"lib": "workspaces:*"
},
"devDependencies": {
"esbuild": "^0.20.2"

View File

@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
ROOT=$DIR/..
rm -rf $ROOT/{cweb_dist/{on,off}chain,dist/tmp/{step{1,2,3},final}}
mkdir -p $ROOT/{cweb_dist/{on,off}chain,dist/tmp/{step{1,2,3},final}}
(
cd $ROOT
cp -rf dist/offchain/. cweb_dist/offchain/
## Bundle and transpile `index.js` so it can be understood by the quickjs interpreter:
echo 'import {cwebMain as f} from "../../../dist/onchain/index"; f();' \
> dist/tmp/step1/onchain.js
yarn esbuild \
--bundle \
--log-level=error \
--format=esm \
dist/tmp/step1/onchain.js \
--outfile=dist/tmp/step2/onchain.js
echo 'import * as std from "std";' |
cat - dist/tmp/step2/onchain.js \
> dist/tmp/step3/onchain.js
yarn esbuild \
--bundle \
--log-level=error \
--format=esm \
--external:std \
--tree-shaking=true \
dist/tmp/step3/onchain.js \
--outfile=dist/tmp/final/onchain.js
cp dist/tmp/final/onchain.js cweb_dist/onchain/index.js
)
rm -rf $ROOT/dist/{tmp,offchain,onchain}

View File

@ -0,0 +1,33 @@
import { readFileSync, writeFileSync } from 'fs';
import { resolve } from 'path';
import { fileURLToPath } from 'url';
const workspaceLink = 'workspaces:*';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
// Update target package.json
const targetPath = resolve(__dirname, '../dist/out/package.json');
const targetPackage = JSON.parse(readFileSync(targetPath, 'utf8'));
// Process all cm- dependencies
Object.keys(targetPackage.dependencies).forEach((dependency) => {
if (dependency.startsWith('cwap-cm-')) {
// Read source version from the corresponding package
const sourcePath = resolve(__dirname, `../../${dependency}/package.json`);
try {
const sourcePackage = JSON.parse(readFileSync(sourcePath, 'utf8'));
const sourceVersion = sourcePackage.version;
if (targetPackage.dependencies[dependency] === workspaceLink) {
targetPackage.dependencies[dependency] = sourceVersion;
} else {
targetPackage.dependencies[dependency] = workspaceLink;
}
} catch (err) {
console.warn(`Warning: Could not read package.json for ${dependency} at ${sourcePath}: ${err.message}`);
}
}
});
// Write back the updated package.json
writeFileSync(targetPath, JSON.stringify(targetPackage, null, 2));

View File

@ -0,0 +1,3 @@
export * from './api';
export * from './uiCommands';
export * from '../shared';

View File

@ -0,0 +1,128 @@
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');
}
return constructUiCommand([
constructCreateOrdersCall({
contractId,
orders: [
[
1,
{
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)]);
};

Some files were not shown because too many files have changed in this diff Show More