Compare commits
24 Commits
ao/generat
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e3c9659af8 | |||
| 99c541a061 | |||
| 748cb6f8f5 | |||
| e9025efa22 | |||
| 84ff2837eb | |||
| 47f21aecdc | |||
| 7baec6736b | |||
| 3f640d90ce | |||
| 02f81660a7 | |||
| 5dd0aef472 | |||
| 0d5850ec25 | |||
| cb4a2fb33d | |||
| 27cd1f50b6 | |||
| 732ca0f292 | |||
| bb6d584ed2 | |||
| 0a753b36d0 | |||
| d9670b314a | |||
| 3ec30e911d | |||
| d2bbf76d90 | |||
| c0e6012d2f | |||
| fee46c3710 | |||
| eecfb6367f | |||
| cdf5b58935 | |||
| 370aedfe12 |
@ -14,9 +14,11 @@ includes:
|
|||||||
blake3: 321b1f88930aead7fe47961c8664005b3441f6502824769bf21eb06c3e5aaba4
|
blake3: 321b1f88930aead7fe47961c8664005b3441f6502824769bf21eb06c3e5aaba4
|
||||||
|
|
||||||
use:
|
use:
|
||||||
- dex-app.cm v0.0.67+test
|
- jump-listener.cm v0.1.8
|
||||||
- eth_offer_maker 0.0.67+devnet
|
- jump_listener_devnet v0.1.8
|
||||||
- jump-listener.cm v0.0.5
|
- jump-forwarder.cm v0.1.5
|
||||||
|
- jump_forwarder_devnet v0.1.5
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interpreters: {}
|
interpreters: {}
|
||||||
@ -31,3 +33,4 @@ contract_instances:
|
|||||||
template: contract.cm v0.0.1
|
template: contract.cm v0.0.1
|
||||||
parameters:
|
parameters:
|
||||||
content: []
|
content: []
|
||||||
|
|
||||||
|
|||||||
@ -4,4 +4,4 @@ NPM_PASSWORD=
|
|||||||
# This is needed when working with WASM modules (nodeLinker: node-modules is mandatory)
|
# This is needed when working with WASM modules (nodeLinker: node-modules is mandatory)
|
||||||
NODE_OPTIONS="--experimental-wasm-modules --no-warnings=ExperimentalWarning --max-old-space-size=8192"
|
NODE_OPTIONS="--experimental-wasm-modules --no-warnings=ExperimentalWarning --max-old-space-size=8192"
|
||||||
|
|
||||||
REGISTRATION_PROFILE=devnet
|
REGISTRATION_PROFILE=test
|
||||||
|
|||||||
@ -21,7 +21,6 @@ export default tseslint.config(
|
|||||||
'**/dapp-ui/src/pages/Bot/**',
|
'**/dapp-ui/src/pages/Bot/**',
|
||||||
'**/dapp-ui/src/pages/BotHedging/**',
|
'**/dapp-ui/src/pages/BotHedging/**',
|
||||||
'**/dapp-ui/src/pages/SwapTool/**',
|
'**/dapp-ui/src/pages/SwapTool/**',
|
||||||
'**/market-maker.cm/perf_test/onchain.cjs',
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// rest of the config by module
|
// rest of the config by module
|
||||||
|
|||||||
@ -8,11 +8,12 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yarn build:cm && yarn build:non-cm",
|
"build": "yarn build:cm && yarn build:non-cm",
|
||||||
"build:production": "yarn build:cm && yarn workspace dapp-ui build:production",
|
"build:production": "yarn build:cm && yarn workspace dapp-ui build:production",
|
||||||
"build:cm": "yarn workspaces foreach -Ap --include 'packages/*.cm' --include 'packages/lib' --include 'packages/cwait' run build",
|
"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/lib' --exclude 'packages/cwait' run build",
|
||||||
"create-index": "cweb-tool create-index -c ./.cweb-config/config.yaml --profile $REGISTRATION_PROFILE",
|
"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",
|
"publish-index": "cweb-tool publish-index -c ./.cweb-config/config.yaml --profile $REGISTRATION_PROFILE",
|
||||||
"deploy-contracts": "yarn publish-actions",
|
"deploy-contracts": "yarn build:cm && yarn publish-actions",
|
||||||
"call-contracts": "yarn call-contracts:prepare && yarn call-contracts:invoke",
|
"call-contracts": "yarn call-contracts:prepare && yarn call-contracts:invoke",
|
||||||
"call-contracts:prepare": "yarn workspaces foreach --all run prepare-for-package && yarn node ./.cweb-config/create-calls.mjs",
|
"call-contracts:prepare": "yarn workspaces foreach --all run prepare-for-package && yarn node ./.cweb-config/create-calls.mjs",
|
||||||
"call-contracts:invoke": "cweb-tool call .cweb-config/calls.yaml -c ./.cweb-config/config.yaml --profile $REGISTRATION_PROFILE",
|
"call-contracts:invoke": "cweb-tool call .cweb-config/calls.yaml -c ./.cweb-config/config.yaml --profile $REGISTRATION_PROFILE",
|
||||||
@ -46,7 +47,7 @@
|
|||||||
"@coinweb/self-register": "0.1.3",
|
"@coinweb/self-register": "0.1.3",
|
||||||
"@coinweb/claims-client": "0.1.6-debug",
|
"@coinweb/claims-client": "0.1.6-debug",
|
||||||
"@coinweb/contract-kit": "0.2.6",
|
"@coinweb/contract-kit": "0.2.6",
|
||||||
"@coinweb/testing-sdk": "0.0.9-remote",
|
"@coinweb/testing-sdk": "0.0.10-mutex",
|
||||||
"@coinweb/minimal-sdk": "1.2.21"
|
"@coinweb/minimal-sdk": "1.2.21"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yarn build:files && yarn pack:all",
|
"build": "yarn build:files && yarn pack:all",
|
||||||
"build:files": "yarn clean && yarn g:tsc -p tsconfig.build.json && NODE_ENV=production yarn g:cweb-tool build-cm .",
|
"build:files": "yarn clean && yarn g:tsc -p tsconfig.build.json && NODE_ENV=production ./scripts/build.sh",
|
||||||
"start": "node src/offchain/index.js",
|
"start": "node src/offchain/index.js",
|
||||||
"test": "yarn g:vitest run --pool=forks",
|
"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: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",
|
||||||
@ -26,6 +26,9 @@
|
|||||||
"bech32": "2.0.0",
|
"bech32": "2.0.0",
|
||||||
"bs58": "6.0.0"
|
"bs58": "6.0.0"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"esbuild": "^0.20.2"
|
||||||
|
},
|
||||||
"main": "cweb_dist/offchain/index.js",
|
"main": "cweb_dist/offchain/index.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
"cweb_interpreter": "a344c6003922f9e44385f6e8234a7d2567d9a676b14330ad3b42cbd1948a92bf"
|
"cweb_interpreter": "a344c6003922f9e44385f6e8234a7d2567d9a676b14330ad3b42cbd1948a92bf"
|
||||||
|
|||||||
41
packages/contract.cm/scripts/build.sh
Executable file
41
packages/contract.cm/scripts/build.sh
Executable 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}
|
||||||
@ -9,5 +9,5 @@ export enum PUBLIC_METHODS {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const FEE = {
|
export const FEE = {
|
||||||
ADD_WORD: 10000n,
|
ADD_WORD: 10000000n,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,6 +9,7 @@ export const constructAddWordUiCommand = ({ word, contractId }: { word: string;
|
|||||||
methodName: PUBLIC_METHODS.ADD_WORD,
|
methodName: PUBLIC_METHODS.ADD_WORD,
|
||||||
methodArgs: [[word] satisfies AddWordArgs],
|
methodArgs: [[word] satisfies AddWordArgs],
|
||||||
cost: FEE.ADD_WORD,
|
cost: FEE.ADD_WORD,
|
||||||
|
withQueue: false,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
94
packages/contract.cm/src/onchain/__tests__/handlers.test.ts
Normal file
94
packages/contract.cm/src/onchain/__tests__/handlers.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
35
packages/contract.cm/src/onchain/__tests__/helpers.ts
Normal file
35
packages/contract.cm/src/onchain/__tests__/helpers.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -1,7 +1,10 @@
|
|||||||
import { constructClaim } from '@coinweb/contract-kit';
|
import { constructClaim } from '@coinweb/contract-kit';
|
||||||
import { storeOp } from 'cwait';
|
import { readOp, storeOp, cwait } from 'cwait';
|
||||||
|
|
||||||
import { AddWordArgs, createWordKey } from '../offchain/shared';
|
import { TypedClaim } from '../../../lib/dist/shared/types';
|
||||||
|
import { AddWordArgs, createWordKey, WordClaimBody } from '../offchain/shared';
|
||||||
|
|
||||||
|
import { extraLogic } from './extraLogic';
|
||||||
|
|
||||||
function hashCode(str: string): string {
|
function hashCode(str: string): string {
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
@ -12,10 +15,40 @@ function hashCode(str: string): string {
|
|||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
hash |= 0;
|
hash |= 0;
|
||||||
}
|
}
|
||||||
return `${hash.toString(16)}`;
|
return `${(hash < 0 ? hash * -1 : hash).toString(16)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addWord = async (...[word]: AddWordArgs) => {
|
export const addWord = cwait(async (...[word]: AddWordArgs) => {
|
||||||
|
console.log('addWord START');
|
||||||
|
|
||||||
const id = hashCode(word);
|
const id = hashCode(word);
|
||||||
storeOp(constructClaim(createWordKey(id), { word }, '0x0'));
|
|
||||||
};
|
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');
|
||||||
|
});
|
||||||
|
|||||||
@ -1,30 +1,9 @@
|
|||||||
import { SELF_REGISTER_HANDLER_NAME, ContractHandlers as CKContractHandlers } from '@coinweb/contract-kit';
|
import { constructCwebMain } from 'cwait';
|
||||||
import { selfRegisterHandler } from '@coinweb/self-register';
|
|
||||||
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 { addWord } from './addWord';
|
import { addWord } from './addWord';
|
||||||
|
|
||||||
const addWrappers = (method: MethodCallback): MethodCallback => {
|
export const cwebMain = constructCwebMain({
|
||||||
return selfCallWrapper(method);
|
[PUBLIC_METHODS.ADD_WORD]: addWord,
|
||||||
};
|
});
|
||||||
|
|
||||||
const createModule = (): ContractHandlers => {
|
|
||||||
const module: ContractHandlers = { handlers: {} };
|
|
||||||
|
|
||||||
addMethodHandler(module, PUBLIC_METHODS.ADD_WORD, addWrappers(executor(addWord)));
|
|
||||||
|
|
||||||
addMethodHandler(module, SELF_REGISTER_HANDLER_NAME, selfRegisterHandler);
|
|
||||||
|
|
||||||
queue.applyQueue(module as CKContractHandlers, [PUBLIC_METHODS.ADD_WORD]);
|
|
||||||
|
|
||||||
return module;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const cwebMain = async () => {
|
|
||||||
const module = createModule();
|
|
||||||
|
|
||||||
await executeHandler(module);
|
|
||||||
};
|
|
||||||
|
|||||||
31
packages/contract.cm/src/onchain/extraLogic.ts
Normal file
31
packages/contract.cm/src/onchain/extraLogic.ts
Normal 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);
|
||||||
|
});
|
||||||
13
packages/contract.cm/src/onchain/extraLogic2.ts
Normal file
13
packages/contract.cm/src/onchain/extraLogic2.ts
Normal 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;
|
||||||
|
});
|
||||||
14
packages/contract.cm/vitest.config.ts
Normal file
14
packages/contract.cm/vitest.config.ts
Normal 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',
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -3,7 +3,8 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yarn g:tsc -p tsconfig.build.json",
|
"clean": "rm -rf dist",
|
||||||
|
"build": "yarn clean && yarn g:tsc -p tsconfig.build.json",
|
||||||
"dev": "yarn g:tsc -p tsconfig.build.json --watch",
|
"dev": "yarn g:tsc -p tsconfig.build.json --watch",
|
||||||
"lint": "yarn g:lint .",
|
"lint": "yarn g:lint .",
|
||||||
"publish:lib": "node scripts/publish.js"
|
"publish:lib": "node scripts/publish.js"
|
||||||
|
|||||||
23
packages/cwait/src/contract-kit/declarations.d.ts
vendored
Normal file
23
packages/cwait/src/contract-kit/declarations.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@coinweb/contract-kit/dist/esm/operations/block' {
|
||||||
|
export * from '@coinweb/contract-kit/dist/types/operations/block';
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@coinweb/contract-kit/dist/esm/operations/call' {
|
||||||
|
export * from '@coinweb/contract-kit/dist/types/operations/call';
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@coinweb/contract-kit/dist/esm/operations/read' {
|
||||||
|
export * from '@coinweb/contract-kit/dist/types/operations/read';
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { extractContinuations, Context } from '@coinweb/contract-kit';
|
import { extractContinuations, Context, MethodCallback as MethodCallbackOrig } from '@coinweb/contract-kit';
|
||||||
import {
|
import {
|
||||||
getContextCall,
|
getContextCall,
|
||||||
getContextGenesis,
|
getContextGenesis,
|
||||||
@ -54,6 +54,18 @@ export async function executeHandler(contractModule: ContractHandlers): Promise<
|
|||||||
};
|
};
|
||||||
const method = getMethodName(context);
|
const method = getMethodName(context);
|
||||||
const handler = getMethodHandler(contractModule, method);
|
const handler = getMethodHandler(contractModule, method);
|
||||||
|
|
||||||
|
console.log('EXECUTE >>>', method);
|
||||||
|
try {
|
||||||
const txs = await handler(context);
|
const txs = await handler(context);
|
||||||
writeToResultFile(txs);
|
writeToResultFile(txs);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.log('ERROR >>>', error.message);
|
||||||
|
console.log('ERROR >>>', error.stack);
|
||||||
|
} else {
|
||||||
|
console.log('ERROR >>>', error);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
packages/cwait/src/contract-kit/store.d.ts
vendored
11
packages/cwait/src/contract-kit/store.d.ts
vendored
@ -1,11 +0,0 @@
|
|||||||
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';
|
|
||||||
}
|
|
||||||
34
packages/cwait/src/onchain/claims/funds.ts
Normal file
34
packages/cwait/src/onchain/claims/funds.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import {
|
||||||
|
constructClaim,
|
||||||
|
constructClaimKey,
|
||||||
|
constructRangeRead,
|
||||||
|
constructStore,
|
||||||
|
constructTake,
|
||||||
|
CwebStore,
|
||||||
|
GRead,
|
||||||
|
GStore,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
import { CwebRead } from '@coinweb/contract-kit/dist/esm/operations/read';
|
||||||
|
import { toHex } from 'lib/shared';
|
||||||
|
|
||||||
|
import { context } from '../context';
|
||||||
|
import { uuid } from '../utils';
|
||||||
|
|
||||||
|
export const fundsKey = '_funds';
|
||||||
|
|
||||||
|
export const constructFundsClaimKeyFirstPart = (processId: string) => [fundsKey, processId];
|
||||||
|
|
||||||
|
export const constructFundsClaimKey = (processId: string, claimId?: string) =>
|
||||||
|
constructClaimKey(constructFundsClaimKeyFirstPart(processId), [claimId ?? uuid()]);
|
||||||
|
|
||||||
|
export const constructFundsClaim = (processId: string, amount: bigint) =>
|
||||||
|
constructClaim(constructFundsClaimKey(processId), {}, toHex(amount));
|
||||||
|
|
||||||
|
export const constructFundsClaimStore = (processId: string, amount: bigint): GStore<CwebStore> =>
|
||||||
|
constructStore(constructFundsClaim(processId, amount));
|
||||||
|
|
||||||
|
export const constructFundsClaimRangRead = (processId: string): GRead<CwebRead> =>
|
||||||
|
constructRangeRead(context.issuer, constructFundsClaimKeyFirstPart(processId), {}, 10000);
|
||||||
|
|
||||||
|
export const constructFundsClaimTakeOp = (processId: string, id: string) =>
|
||||||
|
constructTake(constructFundsClaimKey(processId, id));
|
||||||
2
packages/cwait/src/onchain/claims/index.ts
Normal file
2
packages/cwait/src/onchain/claims/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './funds';
|
||||||
|
export * from './result';
|
||||||
37
packages/cwait/src/onchain/claims/result.ts
Normal file
37
packages/cwait/src/onchain/claims/result.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import {
|
||||||
|
BlockFilter,
|
||||||
|
constructClaim,
|
||||||
|
constructClaimKey,
|
||||||
|
constructStore,
|
||||||
|
constructTake,
|
||||||
|
CwebStore,
|
||||||
|
GStore,
|
||||||
|
constructRead,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
|
||||||
|
import { ResolvedOp } from '../../types';
|
||||||
|
import { context } from '../context';
|
||||||
|
|
||||||
|
export const resultKey = '_result';
|
||||||
|
|
||||||
|
export const constructResultClaimKey = (id: string) => constructClaimKey([resultKey], [id]);
|
||||||
|
|
||||||
|
export const constructResultClaim = (id: string, result: ResolvedOp[]) =>
|
||||||
|
constructClaim(constructResultClaimKey(id), result, '0x0');
|
||||||
|
|
||||||
|
export const constructResultClaimStore = (id: string, result: ResolvedOp[]): GStore<CwebStore> =>
|
||||||
|
constructStore(constructResultClaim(id, result));
|
||||||
|
|
||||||
|
export const constructResultClaimRead = (id: string) => constructRead(context.issuer, constructResultClaimKey(id));
|
||||||
|
|
||||||
|
export const constructResultClaimTake = (id: string) => constructTake(constructResultClaimKey(id));
|
||||||
|
|
||||||
|
export const constructResultBlockFilter = (id: string): BlockFilter => {
|
||||||
|
const { first_part: first, second_part: second } = constructResultClaimKey(id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
issuer: context.issuer,
|
||||||
|
first,
|
||||||
|
second,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,9 +1,38 @@
|
|||||||
import { constructContractIssuer, Context, extractUser, getAuthenticated, getContractId } from '@coinweb/contract-kit';
|
import {
|
||||||
|
Context,
|
||||||
|
ContractIssuer,
|
||||||
|
extractContractArgs,
|
||||||
|
extractUser,
|
||||||
|
getMethodArguments,
|
||||||
|
GTake,
|
||||||
|
User,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take';
|
||||||
|
import { getCallParameters, getContractIssuer } from 'lib/onchain';
|
||||||
|
|
||||||
|
import { ExecutorMethodArgs, ResolvedOp } from '../../types';
|
||||||
|
import { pushAwaitedTask } from '../runtime';
|
||||||
|
|
||||||
|
import { extractOps } from './extractOps';
|
||||||
|
|
||||||
let rawContext: Context | null = null;
|
let rawContext: Context | null = null;
|
||||||
|
|
||||||
export const setRawContext = (ctx: Context) => {
|
const initialContext = {
|
||||||
rawContext = ctx;
|
isChild: false,
|
||||||
|
thisId: '',
|
||||||
|
parentId: '' as string | undefined,
|
||||||
|
ops: [] as ResolvedOp[],
|
||||||
|
funds: {
|
||||||
|
availableCweb: 0n,
|
||||||
|
storedCweb: 0n,
|
||||||
|
takeOps: [] as GTake<CwebTake>[],
|
||||||
|
},
|
||||||
|
methodName: '',
|
||||||
|
initialArgs: [] as unknown[],
|
||||||
|
user: null as User | null,
|
||||||
|
issuer: null as ContractIssuer | null,
|
||||||
|
parentTxId: '',
|
||||||
|
needSaveResult: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRawContext = () => {
|
export const getRawContext = () => {
|
||||||
@ -14,14 +43,111 @@ export const getRawContext = () => {
|
|||||||
return rawContext;
|
return rawContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getMethodArgs = () => {
|
||||||
|
return getMethodArguments(getRawContext()) as [methodName: string, ...ExecutorMethodArgs];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const handleContext = (ctx: Context) => {
|
||||||
|
rawContext = ctx;
|
||||||
|
|
||||||
|
const contractArgs = extractContractArgs(getRawContext().tx);
|
||||||
|
const [
|
||||||
|
methodName,
|
||||||
|
initialArgs,
|
||||||
|
resolvedOps,
|
||||||
|
caller,
|
||||||
|
thisId,
|
||||||
|
parentId,
|
||||||
|
shouldSaveResult,
|
||||||
|
takenFundsIds = [],
|
||||||
|
execOpsIndexes = [],
|
||||||
|
] = getMethodArgs();
|
||||||
|
|
||||||
|
initialContext.isChild = !!parentId;
|
||||||
|
initialContext.thisId =
|
||||||
|
thisId ??
|
||||||
|
(BigInt(`0x${parentId ?? '0'}`) * 3n + BigInt(`0x${getRawContext().call.txid}`)).toString(16).slice(0, 32);
|
||||||
|
initialContext.parentId = parentId;
|
||||||
|
initialContext.methodName = methodName;
|
||||||
|
initialContext.initialArgs = initialArgs ?? [];
|
||||||
|
initialContext.needSaveResult = shouldSaveResult ?? false;
|
||||||
|
|
||||||
|
const { authInfo } = getCallParameters(getRawContext());
|
||||||
|
initialContext.user = (authInfo && extractUser(authInfo)) ?? caller ?? null;
|
||||||
|
|
||||||
|
initialContext.issuer = getContractIssuer(getRawContext());
|
||||||
|
initialContext.parentTxId = getRawContext().call.txid;
|
||||||
|
|
||||||
|
console.log('Parse ops in context');
|
||||||
|
const { extractedOps, executionOpsTakeOp, storedCweb, fundsTakeOps } = extractOps({
|
||||||
|
contractArgs,
|
||||||
|
takenFundsIds,
|
||||||
|
execOpsIndexes,
|
||||||
|
});
|
||||||
|
|
||||||
|
const previousOps = resolvedOps ?? [];
|
||||||
|
const allResolvedOps = [...previousOps, ...extractedOps];
|
||||||
|
console.log('new ops >>>', JSON.stringify(extractedOps));
|
||||||
|
initialContext.ops = allResolvedOps;
|
||||||
|
|
||||||
|
const { availableCweb } = getCallParameters(getRawContext());
|
||||||
|
|
||||||
|
console.log('Available Cweb: ', availableCweb);
|
||||||
|
console.log('Stored Cweb: ', storedCweb);
|
||||||
|
|
||||||
|
initialContext.funds = {
|
||||||
|
availableCweb: availableCweb,
|
||||||
|
storedCweb: storedCweb,
|
||||||
|
takeOps: fundsTakeOps,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (executionOpsTakeOp) {
|
||||||
|
pushAwaitedTask(executionOpsTakeOp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!executionOpsTakeOp; //This is a flag to check if the execution should restart / TODO: refactor;
|
||||||
|
};
|
||||||
|
|
||||||
export const context = {
|
export const context = {
|
||||||
get issuer() {
|
get isChild() {
|
||||||
return constructContractIssuer(getContractId(getRawContext().tx));
|
return initialContext.isChild;
|
||||||
},
|
},
|
||||||
get authenticated() {
|
get thisId() {
|
||||||
return getAuthenticated(getRawContext().tx);
|
return initialContext.thisId;
|
||||||
|
},
|
||||||
|
get parentId() {
|
||||||
|
return initialContext.parentId;
|
||||||
|
},
|
||||||
|
get methodName() {
|
||||||
|
return getMethodArguments(getRawContext())[0] as string;
|
||||||
|
},
|
||||||
|
get initialArgs() {
|
||||||
|
return initialContext.initialArgs;
|
||||||
},
|
},
|
||||||
get user() {
|
get user() {
|
||||||
return extractUser(getAuthenticated(getRawContext().tx));
|
if (!initialContext.user) {
|
||||||
|
throw new Error('User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialContext.user;
|
||||||
|
},
|
||||||
|
get issuer() {
|
||||||
|
if (!initialContext.issuer) {
|
||||||
|
throw new Error('Issuer not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialContext.issuer;
|
||||||
|
},
|
||||||
|
get parentTxId() {
|
||||||
|
return initialContext.parentTxId;
|
||||||
|
},
|
||||||
|
get ops() {
|
||||||
|
return initialContext.ops;
|
||||||
|
},
|
||||||
|
get needSaveResult() {
|
||||||
|
return initialContext.needSaveResult;
|
||||||
|
},
|
||||||
|
get funds() {
|
||||||
|
return initialContext.funds;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
194
packages/cwait/src/onchain/context/extractOps.ts
Normal file
194
packages/cwait/src/onchain/context/extractOps.ts
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import { constructTake, extractRead, GRead, GTake, ResolvedOperation, ResolvedRead } from '@coinweb/contract-kit';
|
||||||
|
import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take';
|
||||||
|
|
||||||
|
import { ResolvedOp, TypedClaim } from '../../types';
|
||||||
|
import { resultKey } from '../claims';
|
||||||
|
import {
|
||||||
|
mutexBlockLockKey,
|
||||||
|
mutexBlockUnlockKey,
|
||||||
|
mutexExecOpsKey,
|
||||||
|
MutexExecOpsResult,
|
||||||
|
MutexNotifyLockState,
|
||||||
|
} from '../mutex';
|
||||||
|
import { isResolvedBlockOp, isResolvedReadOp, isResolvedTakeOp } from '../utils';
|
||||||
|
|
||||||
|
const extractFunds = (fundsOp: GRead<ResolvedRead>, takenFundsIds: string[]) => {
|
||||||
|
if (!fundsOp) {
|
||||||
|
return {
|
||||||
|
takeOps: [],
|
||||||
|
amount: 0n,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const claims = extractRead(fundsOp);
|
||||||
|
|
||||||
|
const newClaims = claims?.filter(({ content }) => !takenFundsIds.includes((content.key.second_part as [string])[0]));
|
||||||
|
|
||||||
|
if (!newClaims?.length) {
|
||||||
|
return {
|
||||||
|
takeOps: [],
|
||||||
|
amount: 0n,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
takeOps: newClaims.map(({ content }) => constructTake(content.key)),
|
||||||
|
amount: newClaims.reduce((acc, { content }) => acc + BigInt(content.fees_stored), 0n),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const extractOps = ({
|
||||||
|
contractArgs,
|
||||||
|
takenFundsIds,
|
||||||
|
execOpsIndexes,
|
||||||
|
}: {
|
||||||
|
contractArgs: ResolvedOperation[];
|
||||||
|
takenFundsIds: string[];
|
||||||
|
execOpsIndexes: number[];
|
||||||
|
}) => {
|
||||||
|
if (!contractArgs.length) {
|
||||||
|
return {
|
||||||
|
extractedOps: [],
|
||||||
|
fundsTakeOps: [],
|
||||||
|
storedCweb: 0n,
|
||||||
|
executionOpsTakeOp: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const [fundsOp, ...resolvedOps] = contractArgs;
|
||||||
|
|
||||||
|
if (!isResolvedReadOp(fundsOp)) {
|
||||||
|
throw new Error('Wrong funds claims');
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractedOps: ResolvedOp[] = [];
|
||||||
|
let executedOps: MutexExecOpsResult = [];
|
||||||
|
let executionOpsProfit: bigint = 0n;
|
||||||
|
let executionOpsTakeOp: GTake<CwebTake> | null = null;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < resolvedOps.length) {
|
||||||
|
const op = resolvedOps[i];
|
||||||
|
|
||||||
|
if (isResolvedBlockOp(op)) {
|
||||||
|
const { first } = op.BlockOp.blocks_on[0][0];
|
||||||
|
|
||||||
|
console.log('first >>>', JSON.stringify(first));
|
||||||
|
|
||||||
|
//Maybe it is needed to check more conditions here
|
||||||
|
if (Array.isArray(first)) {
|
||||||
|
switch (first[0]) {
|
||||||
|
case resultKey: {
|
||||||
|
const nextAfterBlock = resolvedOps[i + 1];
|
||||||
|
|
||||||
|
if (!isResolvedTakeOp(nextAfterBlock)) {
|
||||||
|
throw new Error('Wrong subcall result');
|
||||||
|
}
|
||||||
|
|
||||||
|
extractedOps.push({
|
||||||
|
ChildOp: {
|
||||||
|
ops: (nextAfterBlock.TakeOp.result as TypedClaim<ResolvedOp[]>).body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case mutexBlockLockKey: {
|
||||||
|
const nextAfterBlock = resolvedOps[i + 1];
|
||||||
|
|
||||||
|
if (!isResolvedTakeOp(nextAfterBlock)) {
|
||||||
|
throw new Error('Wrong mutex lock result');
|
||||||
|
}
|
||||||
|
|
||||||
|
extractedOps.push({
|
||||||
|
LockOp: nextAfterBlock.TakeOp.result.body as MutexNotifyLockState,
|
||||||
|
});
|
||||||
|
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case mutexBlockUnlockKey: {
|
||||||
|
const nextAfterBlock = resolvedOps[i + 1];
|
||||||
|
|
||||||
|
if (!isResolvedTakeOp(nextAfterBlock)) {
|
||||||
|
throw new Error('Wrong mutex unlock result');
|
||||||
|
}
|
||||||
|
|
||||||
|
extractedOps.push({ UnlockOp: 0 });
|
||||||
|
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case mutexExecOpsKey: {
|
||||||
|
const nextAfterBlock = resolvedOps[i + 1];
|
||||||
|
|
||||||
|
const execOpResultClaim = extractRead(nextAfterBlock)?.[0]?.content;
|
||||||
|
|
||||||
|
if (!execOpResultClaim) {
|
||||||
|
throw new Error('Wrong mutex exec result claim');
|
||||||
|
}
|
||||||
|
|
||||||
|
executedOps = execOpResultClaim.body as MutexExecOpsResult;
|
||||||
|
executionOpsProfit = BigInt(execOpResultClaim.fees_stored);
|
||||||
|
executionOpsTakeOp = constructTake(execOpResultClaim.key);
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isResolvedTakeOp(op)) {
|
||||||
|
const keyFirstPart = op.TakeOp.result.key.first_part;
|
||||||
|
if (Array.isArray(keyFirstPart) && keyFirstPart[0] === mutexExecOpsKey) {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extractedOps.push(op);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allOps: ResolvedOp[] = [];
|
||||||
|
|
||||||
|
const totalOpsCount = extractedOps.length + executedOps.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < totalOpsCount; i++) {
|
||||||
|
if (execOpsIndexes.includes(i)) {
|
||||||
|
const op = executedOps.shift();
|
||||||
|
|
||||||
|
if (!op) {
|
||||||
|
throw new Error('Wrong mutex exec result place');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op.ok) {
|
||||||
|
allOps.push(op.resolved);
|
||||||
|
} else {
|
||||||
|
allOps.push({ SlotOp: { ok: false, error: op.error } });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const op = extractedOps.shift();
|
||||||
|
|
||||||
|
if (!op) {
|
||||||
|
throw new Error('Contract call args parsing error');
|
||||||
|
}
|
||||||
|
|
||||||
|
allOps.push(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { takeOps, amount } = extractFunds(fundsOp, takenFundsIds);
|
||||||
|
|
||||||
|
return {
|
||||||
|
fundsTakeOps: takeOps,
|
||||||
|
storedCweb: amount + executionOpsProfit,
|
||||||
|
extractedOps: allOps,
|
||||||
|
executionOpsTakeOp,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,84 +0,0 @@
|
|||||||
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();
|
|
||||||
};
|
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import { getAwaitedTasks } from '../../runtime';
|
||||||
|
|
||||||
|
import { prepareTx } from './prepareTxs';
|
||||||
|
|
||||||
|
export const constructTx = (isFullyExecuted: boolean) => {
|
||||||
|
const awaitedTasks = getAwaitedTasks();
|
||||||
|
|
||||||
|
const { calls, txFee } = prepareTx({ awaitedTasks, isFullyExecuted, txFee: 0n });
|
||||||
|
|
||||||
|
const { txs } = prepareTx({ awaitedTasks, isFullyExecuted, txFee, callsCount: calls });
|
||||||
|
|
||||||
|
return txs;
|
||||||
|
};
|
||||||
1
packages/cwait/src/onchain/executor/constructTx/index.ts
Normal file
1
packages/cwait/src/onchain/executor/constructTx/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './constructTx';
|
||||||
@ -0,0 +1,292 @@
|
|||||||
|
import {
|
||||||
|
constructBlock,
|
||||||
|
constructContinueTx,
|
||||||
|
constructContractRef,
|
||||||
|
FullCallInfo,
|
||||||
|
GTake,
|
||||||
|
NewTx,
|
||||||
|
passCwebFrom,
|
||||||
|
PreparedOperation,
|
||||||
|
sendCwebInterface,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take';
|
||||||
|
|
||||||
|
import { ExecutorMethodArgs, PreparedExtendedStoreOp, PreparedOp, ResolvedSlotOp } from '../../../types';
|
||||||
|
import { constructFundsClaimRangRead, constructFundsClaimStore } from '../../claims/funds';
|
||||||
|
import { constructResultBlockFilter, constructResultClaimTake, resultKey } from '../../claims/result';
|
||||||
|
import { context, getRawContext } from '../../context';
|
||||||
|
import { constructExecOpsCall, constructLockCall, constructUnlockCall, mutexExecOpsKey } from '../../mutex';
|
||||||
|
import {
|
||||||
|
isPreparedExecOp,
|
||||||
|
isPreparedLockOp,
|
||||||
|
isPreparedStoreOp,
|
||||||
|
isPreparedTakeOp,
|
||||||
|
isPreparedUnlockOp,
|
||||||
|
} from '../../utils/opTypeGuards';
|
||||||
|
import { uuid } from '../../utils/uuid';
|
||||||
|
|
||||||
|
export const prepareInThreadTxs = ({
|
||||||
|
cwebPerCall,
|
||||||
|
outThreadTasksCount,
|
||||||
|
outThreadFee,
|
||||||
|
ops,
|
||||||
|
}: {
|
||||||
|
ops: PreparedOp[];
|
||||||
|
cwebPerCall: bigint;
|
||||||
|
outThreadTasksCount: number;
|
||||||
|
outThreadFee: bigint;
|
||||||
|
}): {
|
||||||
|
txs: NewTx[];
|
||||||
|
calls: number;
|
||||||
|
txFee: bigint;
|
||||||
|
} => {
|
||||||
|
if (!ops.length) {
|
||||||
|
const { constructSendCweb } = sendCwebInterface();
|
||||||
|
const { availableCweb, takeOps, storedCweb } = context.funds;
|
||||||
|
const { user } = context;
|
||||||
|
|
||||||
|
const restOfAvailableCweb = availableCweb - outThreadFee;
|
||||||
|
|
||||||
|
const fee = BigInt(takeOps.length) * 100n + 3000n; //TODO: Remove magic numbers
|
||||||
|
const restOfCweb = restOfAvailableCweb + storedCweb - fee;
|
||||||
|
|
||||||
|
const txs =
|
||||||
|
restOfCweb > 0n
|
||||||
|
? [
|
||||||
|
constructContinueTx(getRawContext(), [
|
||||||
|
passCwebFrom(context.issuer, restOfAvailableCweb),
|
||||||
|
...takeOps,
|
||||||
|
...constructSendCweb(restOfCweb, user, null),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return { txs, calls: 0, txFee: fee };
|
||||||
|
}
|
||||||
|
|
||||||
|
let txFee = 0n;
|
||||||
|
let callsPrepared = 0;
|
||||||
|
const resolvedSlotOps = new Array(outThreadTasksCount).fill({ SlotOp: { ok: true } }) satisfies ResolvedSlotOp[];
|
||||||
|
|
||||||
|
//Mutex exec ops
|
||||||
|
const preparedExecOps: (PreparedExtendedStoreOp | GTake<CwebTake>)[] = [];
|
||||||
|
const excOpsIndexes: number[] = [];
|
||||||
|
|
||||||
|
// const resolvedChildOps: ResolvedOp[] = [...context.ops, ...resolvedSlotOps];
|
||||||
|
//Children
|
||||||
|
|
||||||
|
//Arg for the main call
|
||||||
|
const callArgs: PreparedOperation[] = [];
|
||||||
|
|
||||||
|
//Info for separate parallel calls
|
||||||
|
const parallelCalls: FullCallInfo[] = [];
|
||||||
|
|
||||||
|
const outThreadOps: PreparedOperation[] = [];
|
||||||
|
|
||||||
|
ops.forEach((op, i) => {
|
||||||
|
switch (true) {
|
||||||
|
case isPreparedExecOp(op): {
|
||||||
|
const id = op.ExecOp.id;
|
||||||
|
|
||||||
|
callArgs.push(constructBlock([constructResultBlockFilter(id)]), constructResultClaimTake(id));
|
||||||
|
txFee += 200n;
|
||||||
|
|
||||||
|
const childOps = [
|
||||||
|
...context.ops,
|
||||||
|
...resolvedSlotOps,
|
||||||
|
...ops.map((_, j) => (i === j ? { ExecOp: { id } } : { SlotOp: { ok: true } })),
|
||||||
|
];
|
||||||
|
|
||||||
|
parallelCalls.push({
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(context.issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: context.methodName,
|
||||||
|
methodArgs: [
|
||||||
|
context.initialArgs,
|
||||||
|
childOps,
|
||||||
|
context.user,
|
||||||
|
id,
|
||||||
|
context.thisId,
|
||||||
|
true,
|
||||||
|
] satisfies ExecutorMethodArgs,
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb: cwebPerCall,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
callsPrepared++;
|
||||||
|
txFee += 700n;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case isPreparedStoreOp(op): {
|
||||||
|
if ((op.StoreOp.key.first_part as [string])[0] !== resultKey) {
|
||||||
|
preparedExecOps.push(op);
|
||||||
|
excOpsIndexes.push(i);
|
||||||
|
} else {
|
||||||
|
callArgs.push(op);
|
||||||
|
txFee += 100n;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case isPreparedTakeOp(op): {
|
||||||
|
//TODO: simplify to decrease coupling
|
||||||
|
if ((op.TakeOp.key.first_part as [string])[0] !== mutexExecOpsKey) {
|
||||||
|
preparedExecOps.push(op);
|
||||||
|
excOpsIndexes.push(i);
|
||||||
|
} else {
|
||||||
|
callArgs.push(op);
|
||||||
|
txFee += 100n;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case isPreparedLockOp(op): {
|
||||||
|
const {
|
||||||
|
callInfo,
|
||||||
|
fee,
|
||||||
|
inThreadOps,
|
||||||
|
outThreadOps: outThreadOpsFromLockCall,
|
||||||
|
} = constructLockCall({
|
||||||
|
issuer: context.issuer,
|
||||||
|
lockId: op.LockOp.lockId,
|
||||||
|
timestamp: op.LockOp.timestamp,
|
||||||
|
keys: op.LockOp.keys,
|
||||||
|
processId: context.thisId,
|
||||||
|
});
|
||||||
|
|
||||||
|
parallelCalls.push({ callInfo });
|
||||||
|
callArgs.push(...inThreadOps);
|
||||||
|
txFee += fee;
|
||||||
|
|
||||||
|
outThreadOps.push(...outThreadOpsFromLockCall);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case isPreparedUnlockOp(op): {
|
||||||
|
console.log('prepareInThreadTxs >>> unlockOp');
|
||||||
|
const { callInfo, fee, ops } = constructUnlockCall(
|
||||||
|
context.issuer,
|
||||||
|
op.UnlockOp.lockId,
|
||||||
|
op.UnlockOp.timestamp,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
parallelCalls.push({ callInfo });
|
||||||
|
callArgs.push(...ops);
|
||||||
|
txFee += fee;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
callArgs.push(op);
|
||||||
|
txFee += 100n;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (preparedExecOps.length > 0) {
|
||||||
|
const execId = uuid();
|
||||||
|
|
||||||
|
const { callInfo, fee, ops } = constructExecOpsCall({
|
||||||
|
issuer: context.issuer,
|
||||||
|
ops: preparedExecOps,
|
||||||
|
processId: context.thisId,
|
||||||
|
execId,
|
||||||
|
});
|
||||||
|
|
||||||
|
parallelCalls.push({ callInfo });
|
||||||
|
txFee += fee;
|
||||||
|
|
||||||
|
console.log(txFee);
|
||||||
|
console.log(context.funds.availableCweb);
|
||||||
|
|
||||||
|
callArgs.push(...ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnTxs: NewTx[] = [];
|
||||||
|
|
||||||
|
if (callArgs.length) {
|
||||||
|
const { storedCweb, takeOps } = context.funds;
|
||||||
|
|
||||||
|
const latestCallArg = callArgs.at(-1)!;
|
||||||
|
|
||||||
|
if ('StoreOp' in latestCallArg && (latestCallArg.StoreOp.key.first_part as [string])[0] === resultKey) {
|
||||||
|
//SAVE RESULT CLAIMS
|
||||||
|
console.log('SAVE RESULT CLAIMS');
|
||||||
|
if (callArgs.length > 1) {
|
||||||
|
throw new Error('Unexpected count of result ops');
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultOps = [];
|
||||||
|
|
||||||
|
if (context.needSaveResult) {
|
||||||
|
resultOps.push(...callArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.parentId) {
|
||||||
|
const { availableCweb, storedCweb, takeOps } = context.funds;
|
||||||
|
|
||||||
|
const cwebToStore = availableCweb + storedCweb - BigInt(takeOps.length) * 100n - 500n;
|
||||||
|
|
||||||
|
console.log('cwebToStore: ', cwebToStore);
|
||||||
|
|
||||||
|
resultOps.push(
|
||||||
|
passCwebFrom(context.issuer, availableCweb),
|
||||||
|
...takeOps,
|
||||||
|
constructFundsClaimStore(context.parentId, cwebToStore)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultOps.length) {
|
||||||
|
returnTxs.push(constructContinueTx(getRawContext(), resultOps));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
txFee += 800n + outThreadFee + BigInt(takeOps.length) * 100n;
|
||||||
|
callsPrepared++;
|
||||||
|
|
||||||
|
console.log('provided cweb: ', cwebPerCall - txFee + storedCweb);
|
||||||
|
|
||||||
|
returnTxs.push(
|
||||||
|
constructContinueTx(
|
||||||
|
getRawContext(),
|
||||||
|
[passCwebFrom(context.issuer, cwebPerCall - outThreadFee), ...takeOps],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(context.issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: context.methodName,
|
||||||
|
methodArgs: [
|
||||||
|
context.initialArgs,
|
||||||
|
[...context.ops, ...resolvedSlotOps],
|
||||||
|
context.user,
|
||||||
|
context.thisId,
|
||||||
|
context.parentId,
|
||||||
|
context.needSaveResult,
|
||||||
|
takeOps.map((op) => (op.TakeOp.key.second_part as [string])[0]),
|
||||||
|
excOpsIndexes,
|
||||||
|
] satisfies ExecutorMethodArgs,
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb: cwebPerCall - txFee + storedCweb,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: [constructFundsClaimRangRead(context.thisId), ...callArgs],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parallelCalls.length || outThreadOps.length) {
|
||||||
|
returnTxs.push(constructContinueTx(getRawContext(), outThreadOps, parallelCalls));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { txs: returnTxs, calls: callsPrepared, txFee };
|
||||||
|
};
|
||||||
@ -0,0 +1,141 @@
|
|||||||
|
import {
|
||||||
|
constructContinueTx,
|
||||||
|
constructContractRef,
|
||||||
|
FullCallInfo,
|
||||||
|
GTake,
|
||||||
|
NewTx,
|
||||||
|
PreparedOperation,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take';
|
||||||
|
|
||||||
|
import { ExecutorMethodArgs, PreparedExtendedStoreOp, PreparedOp } from '../../../types';
|
||||||
|
import { context, getRawContext } from '../../context';
|
||||||
|
import { constructLockCall, constructExecOpsCall, constructUnlockCall } from '../../mutex';
|
||||||
|
import {
|
||||||
|
isPreparedBlockOp,
|
||||||
|
isPreparedExecOp,
|
||||||
|
isPreparedLockOp,
|
||||||
|
isPreparedStoreOp,
|
||||||
|
isPreparedTakeOp,
|
||||||
|
isPreparedUnlockOp,
|
||||||
|
} from '../../utils/opTypeGuards';
|
||||||
|
|
||||||
|
export const prepareOutThreadTxs = ({
|
||||||
|
ops,
|
||||||
|
cwebPerCall,
|
||||||
|
}: {
|
||||||
|
ops: PreparedOp[];
|
||||||
|
cwebPerCall: bigint;
|
||||||
|
}): {
|
||||||
|
txs: NewTx[];
|
||||||
|
txFee: bigint;
|
||||||
|
calls: number;
|
||||||
|
} => {
|
||||||
|
const siblingCallResolvedOps = [...context.ops];
|
||||||
|
|
||||||
|
const preparedCalls: FullCallInfo[] = [];
|
||||||
|
const preparedOps: PreparedOperation[] = [];
|
||||||
|
const preparedExecOps: (PreparedExtendedStoreOp | GTake<CwebTake>)[] = [];
|
||||||
|
|
||||||
|
let txFee = 0n;
|
||||||
|
let callsPrepared = 0;
|
||||||
|
|
||||||
|
ops.forEach((op) => {
|
||||||
|
switch (true) {
|
||||||
|
case isPreparedExecOp(op): {
|
||||||
|
const id = op.ExecOp.id;
|
||||||
|
|
||||||
|
const callInfo = {
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(context.issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: context.methodName,
|
||||||
|
methodArgs: [
|
||||||
|
context.initialArgs,
|
||||||
|
[...siblingCallResolvedOps, { ExecOp: { id } }],
|
||||||
|
context.user,
|
||||||
|
id,
|
||||||
|
context.parentId ?? context.thisId,
|
||||||
|
false,
|
||||||
|
] satisfies ExecutorMethodArgs,
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb: cwebPerCall,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
preparedCalls.push(callInfo);
|
||||||
|
txFee += 700n;
|
||||||
|
|
||||||
|
callsPrepared++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case isPreparedStoreOp(op):
|
||||||
|
case isPreparedTakeOp(op): {
|
||||||
|
preparedExecOps.push(op);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case isPreparedLockOp(op): {
|
||||||
|
const { callInfo, fee, outThreadOps } = constructLockCall({
|
||||||
|
issuer: context.issuer,
|
||||||
|
lockId: op.LockOp.lockId,
|
||||||
|
timestamp: op.LockOp.timestamp,
|
||||||
|
keys: op.LockOp.keys,
|
||||||
|
processId: context.thisId,
|
||||||
|
});
|
||||||
|
|
||||||
|
preparedCalls.push({ callInfo });
|
||||||
|
txFee += fee;
|
||||||
|
|
||||||
|
preparedOps.push(...outThreadOps);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case isPreparedUnlockOp(op): {
|
||||||
|
console.log('prepareOutThreadTxs >>> unlockOp');
|
||||||
|
const { callInfo, fee } = constructUnlockCall(context.issuer, op.UnlockOp.lockId, op.UnlockOp.timestamp, false);
|
||||||
|
|
||||||
|
preparedCalls.push({ callInfo });
|
||||||
|
txFee += fee;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case isPreparedBlockOp(op): {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
preparedOps.push(op);
|
||||||
|
txFee += 100n;
|
||||||
|
}
|
||||||
|
|
||||||
|
siblingCallResolvedOps.push({ SlotOp: { ok: true } });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (preparedExecOps.length > 0) {
|
||||||
|
const { callInfo, fee } = constructExecOpsCall({
|
||||||
|
issuer: context.issuer,
|
||||||
|
ops: preparedExecOps,
|
||||||
|
processId: context.thisId,
|
||||||
|
});
|
||||||
|
|
||||||
|
preparedCalls.push({ callInfo });
|
||||||
|
txFee += fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preparedCalls.length > 0 || preparedOps.length > 0) {
|
||||||
|
return {
|
||||||
|
txs: [constructContinueTx(getRawContext(), preparedOps, preparedCalls)],
|
||||||
|
txFee,
|
||||||
|
calls: callsPrepared,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
txs: [],
|
||||||
|
txFee: 0n,
|
||||||
|
calls: 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
import { constructContinueTx, NewTx, passCwebFrom, sendCwebInterface } from '@coinweb/contract-kit';
|
||||||
|
|
||||||
|
import { Task } from '../../../types';
|
||||||
|
import { context, getRawContext } from '../../context';
|
||||||
|
|
||||||
|
import { prepareInThreadTxs } from './prepareInThreadTxs';
|
||||||
|
import { prepareOutThreadTxs } from './prepareOutThreadTxs';
|
||||||
|
import { splitTasks } from './splitTasks';
|
||||||
|
|
||||||
|
export const prepareTx = ({
|
||||||
|
awaitedTasks,
|
||||||
|
isFullyExecuted,
|
||||||
|
txFee,
|
||||||
|
callsCount,
|
||||||
|
}: {
|
||||||
|
awaitedTasks: Task[];
|
||||||
|
isFullyExecuted: boolean;
|
||||||
|
txFee: bigint;
|
||||||
|
callsCount?: number;
|
||||||
|
}): { txs: NewTx[]; calls: number; txFee: bigint } => {
|
||||||
|
if (!awaitedTasks.length) {
|
||||||
|
if (context.isChild) {
|
||||||
|
return { txs: [], calls: 0, txFee: 0n };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { constructSendCweb } = sendCwebInterface();
|
||||||
|
const { availableCweb, takeOps, storedCweb } = context.funds;
|
||||||
|
const { user } = context;
|
||||||
|
|
||||||
|
const fee = BigInt(takeOps.length) * 100n + 3000n; //TODO: Remove magic numbers
|
||||||
|
const restOfCweb = availableCweb + storedCweb - fee;
|
||||||
|
|
||||||
|
const txs =
|
||||||
|
restOfCweb > 0n
|
||||||
|
? [
|
||||||
|
constructContinueTx(getRawContext(), [
|
||||||
|
passCwebFrom(context.issuer, availableCweb),
|
||||||
|
...takeOps,
|
||||||
|
...constructSendCweb(restOfCweb, user, null),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return { txs, calls: 0, txFee: fee };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { inThreadOps, outThreadOps } = splitTasks(awaitedTasks, isFullyExecuted);
|
||||||
|
|
||||||
|
const { availableCweb } = context.funds;
|
||||||
|
|
||||||
|
const cwebPerCall = (availableCweb - txFee) / BigInt(callsCount || 1);
|
||||||
|
|
||||||
|
const {
|
||||||
|
txs: outThreadTxs,
|
||||||
|
calls: outThreadCallsPrepared,
|
||||||
|
txFee: outThreadFee,
|
||||||
|
} = prepareOutThreadTxs({ ops: outThreadOps, cwebPerCall });
|
||||||
|
|
||||||
|
const {
|
||||||
|
txs: inThreadTxs,
|
||||||
|
calls: inThreadCallsPrepared,
|
||||||
|
txFee: inThreadFee,
|
||||||
|
} = prepareInThreadTxs({
|
||||||
|
ops: inThreadOps,
|
||||||
|
cwebPerCall,
|
||||||
|
outThreadTasksCount: outThreadOps.length,
|
||||||
|
outThreadFee,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
txs: [...inThreadTxs, ...outThreadTxs],
|
||||||
|
calls: inThreadCallsPrepared + outThreadCallsPrepared,
|
||||||
|
txFee: inThreadFee + outThreadFee,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { PreparedOp, Task } from '../../../types';
|
||||||
|
|
||||||
|
export const splitTasks = (awaitedTasks: Task[], isFullyExecuted: boolean) => {
|
||||||
|
if (awaitedTasks.length === 0) {
|
||||||
|
return { inThreadOps: [], outThreadOps: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const inThreadOps: PreparedOp[] = [];
|
||||||
|
let outThreadOps: PreparedOp[] = [];
|
||||||
|
|
||||||
|
if (!isFullyExecuted) {
|
||||||
|
const latestTaskBatchId = awaitedTasks.at(-1)!.batchId ?? -1;
|
||||||
|
|
||||||
|
awaitedTasks.forEach((task) => {
|
||||||
|
if (task.batchId === latestTaskBatchId) {
|
||||||
|
inThreadOps.push(task.op);
|
||||||
|
} else {
|
||||||
|
outThreadOps.push(task.op);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
outThreadOps = awaitedTasks.map((task) => task.op);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { inThreadOps, outThreadOps };
|
||||||
|
};
|
||||||
42
packages/cwait/src/onchain/executor/executor.ts
Normal file
42
packages/cwait/src/onchain/executor/executor.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { Context, NewTx, getMethodArguments, isSelfCall } from '@coinweb/contract-kit';
|
||||||
|
|
||||||
|
import { context, getRawContext, handleContext } from '../context';
|
||||||
|
import { execLoop, setNextExec, stopExecution } from '../runtime';
|
||||||
|
import { pushResolvedOp } from '../runtime/resolvedOps';
|
||||||
|
|
||||||
|
import { constructTx } from './constructTx';
|
||||||
|
|
||||||
|
export const executor = (method: (...args: any[]) => Promise<void>) => {
|
||||||
|
return async (ctx: Context): Promise<NewTx[]> => {
|
||||||
|
console.log('executor-start >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');
|
||||||
|
const shouldRestart = handleContext(ctx);
|
||||||
|
pushResolvedOp(context.ops);
|
||||||
|
|
||||||
|
if (getMethodArguments(getRawContext()).length > 2 && !isSelfCall(ctx)) {
|
||||||
|
throw new Error('Wrong contract call, check the call arguments');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldRestart) {
|
||||||
|
console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-finish-restart');
|
||||||
|
return constructTx(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
|
os.setTimeout(() => {
|
||||||
|
stopExecution();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
setNextExec(() => method(...context.initialArgs), []);
|
||||||
|
const isFullyExecuted = await execLoop();
|
||||||
|
console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-finish-end');
|
||||||
|
return constructTx(isFullyExecuted);
|
||||||
|
} catch (error) {
|
||||||
|
console.log((error as Error).message);
|
||||||
|
console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-error');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
1
packages/cwait/src/onchain/executor/index.ts
Normal file
1
packages/cwait/src/onchain/executor/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './executor';
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { ContractHandlers as ContractHandlersOrig, SELF_REGISTER_HANDLER_NAME } from '@coinweb/contract-kit';
|
||||||
|
import { selfRegisterHandler } from '@coinweb/self-register';
|
||||||
|
import { queue } from 'lib/onchain';
|
||||||
|
|
||||||
|
import { addMethodHandler, ContractHandlers, executeHandler } from '../../../contract-kit';
|
||||||
|
import { executor } from '../../executor';
|
||||||
|
import { mutexMethods } from '../../mutex';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const constructCwebMain = (methods: Record<string, (...args: any[]) => Promise<void>>) => async () => {
|
||||||
|
const module: ContractHandlers = { handlers: {} };
|
||||||
|
|
||||||
|
Object.entries(methods).forEach(([name, handler]) => {
|
||||||
|
if (name.startsWith('_')) {
|
||||||
|
throw new Error('Do not start method names with "_" because they are reserved for internal use');
|
||||||
|
}
|
||||||
|
|
||||||
|
addMethodHandler(module, name, executor(handler));
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.entries(mutexMethods).forEach(([name, handler]) => {
|
||||||
|
addMethodHandler(module, name, handler);
|
||||||
|
});
|
||||||
|
|
||||||
|
addMethodHandler(module, SELF_REGISTER_HANDLER_NAME, selfRegisterHandler);
|
||||||
|
|
||||||
|
queue.applyQueue(module as ContractHandlersOrig, []);
|
||||||
|
|
||||||
|
await executeHandler(module);
|
||||||
|
};
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './constructCwebMain';
|
||||||
71
packages/cwait/src/onchain/features/cwait/cwait.ts
Normal file
71
packages/cwait/src/onchain/features/cwait/cwait.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { constructStore } from '@coinweb/contract-kit/dist/esm/operations/store';
|
||||||
|
|
||||||
|
import { constructResultClaim } from '../../claims/result';
|
||||||
|
import { opMarker } from '../../globals/promise';
|
||||||
|
import { setNextExec, stopExecution } from '../../runtime';
|
||||||
|
import { getAwaitedTasksCount, pushAwaitedTask } from '../../runtime/awaitedTasks';
|
||||||
|
import { getUsedOps, startSavingUsedOps, stopSavingUsedOps, shiftResolvedOp } from '../../runtime/resolvedOps';
|
||||||
|
import { isResolvedChildOp, isResolvedExecOp, isResolvedSlotOp } from '../../utils';
|
||||||
|
import { uuid } from '../../utils';
|
||||||
|
|
||||||
|
let isRootDetected = false;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const cwait = <TAsyncCallback extends (...args: any[]) => Promise<unknown>>(asyncCallback: TAsyncCallback) => {
|
||||||
|
let isRoot = false;
|
||||||
|
|
||||||
|
return (async (...args: Parameters<TAsyncCallback>) => {
|
||||||
|
if (!isRootDetected) {
|
||||||
|
isRootDetected = true;
|
||||||
|
isRoot = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRoot) {
|
||||||
|
return asyncCallback(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { op, isOp } = shiftResolvedOp();
|
||||||
|
|
||||||
|
if (!isOp) {
|
||||||
|
pushAwaitedTask({
|
||||||
|
ExecOp: {
|
||||||
|
id: uuid(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = new Promise(() => null) as Promise<unknown> & { [opMarker]: boolean };
|
||||||
|
|
||||||
|
result[opMarker] = true;
|
||||||
|
|
||||||
|
return result as ReturnType<TAsyncCallback>;
|
||||||
|
} else {
|
||||||
|
if (isResolvedSlotOp(op)) {
|
||||||
|
return new Promise(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isResolvedExecOp(op)) {
|
||||||
|
setNextExec(async () => {
|
||||||
|
startSavingUsedOps();
|
||||||
|
await asyncCallback(...args);
|
||||||
|
stopSavingUsedOps();
|
||||||
|
|
||||||
|
if (!getAwaitedTasksCount()) {
|
||||||
|
console.log('push result claim');
|
||||||
|
|
||||||
|
pushAwaitedTask(constructStore(constructResultClaim(op.ExecOp.id, getUsedOps())));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
stopExecution(); //Check: maybe does no affect
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isResolvedChildOp(op)) {
|
||||||
|
return setNextExec(() => asyncCallback(...args), op.ChildOp.ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Exec or Child operation not found');
|
||||||
|
}
|
||||||
|
}) as TAsyncCallback;
|
||||||
|
};
|
||||||
1
packages/cwait/src/onchain/features/cwait/index.ts
Normal file
1
packages/cwait/src/onchain/features/cwait/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './cwait';
|
||||||
4
packages/cwait/src/onchain/features/index.ts
Normal file
4
packages/cwait/src/onchain/features/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './cwait';
|
||||||
|
export * from './mutex';
|
||||||
|
export * from './ops';
|
||||||
|
export * from './constructCwebMain';
|
||||||
1
packages/cwait/src/onchain/features/mutex/index.ts
Normal file
1
packages/cwait/src/onchain/features/mutex/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './lock';
|
||||||
78
packages/cwait/src/onchain/features/mutex/lock.ts
Normal file
78
packages/cwait/src/onchain/features/mutex/lock.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { getTime } from 'lib/onchain';
|
||||||
|
|
||||||
|
import { opMarker } from '../../globals/promise';
|
||||||
|
import { LockedKey } from '../../mutex';
|
||||||
|
import { pushAwaitedTask, shiftResolvedOp } from '../../runtime';
|
||||||
|
import { isResolvedLockOp, isResolvedSlotOp, isResolvedUnlockOp, uuid } from '../../utils';
|
||||||
|
|
||||||
|
const unlock = (lockId: string, timestamp: number) => {
|
||||||
|
console.log('unlockOp');
|
||||||
|
let opMarkerValue = false;
|
||||||
|
|
||||||
|
const result = new Promise<void>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const { op, isOp } = shiftResolvedOp();
|
||||||
|
|
||||||
|
if (!isOp) {
|
||||||
|
pushAwaitedTask({ UnlockOp: { lockId, timestamp } });
|
||||||
|
opMarkerValue = true;
|
||||||
|
} else {
|
||||||
|
if (isResolvedSlotOp(op)) {
|
||||||
|
console.log('unlockOp-slotOp');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isResolvedUnlockOp(op)) {
|
||||||
|
console.log('unlockOp-error');
|
||||||
|
throw new Error('Unlock operation not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
(result as Promise<void> & { [opMarker]: boolean })[opMarker] = opMarkerValue;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const lock = (keys: LockedKey[] | LockedKey) => {
|
||||||
|
console.log('lockOp');
|
||||||
|
let opMarkerValue = false;
|
||||||
|
|
||||||
|
const result = new Promise<() => Promise<void>>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const { op, isOp } = shiftResolvedOp();
|
||||||
|
|
||||||
|
if (!isOp) {
|
||||||
|
pushAwaitedTask({
|
||||||
|
LockOp: { lockId: uuid(), keys: Array.isArray(keys) ? keys : [keys], timestamp: getTime() },
|
||||||
|
});
|
||||||
|
opMarkerValue = true;
|
||||||
|
} else {
|
||||||
|
if (isResolvedSlotOp(op)) {
|
||||||
|
console.log('lockOp-slotOp');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isResolvedLockOp(op)) {
|
||||||
|
console.log('lockOp-error');
|
||||||
|
throw new Error('Lock operation not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = op.LockOp;
|
||||||
|
|
||||||
|
resolve(() => unlock(result.lockId, result.timestamp));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
(result as Promise<() => Promise<void>> & { [opMarker]: boolean })[opMarker] = opMarkerValue;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
38
packages/cwait/src/onchain/features/ops/block.ts
Normal file
38
packages/cwait/src/onchain/features/ops/block.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { BlockFilter, constructBlock, extractBlock } from '@coinweb/contract-kit';
|
||||||
|
|
||||||
|
import { opMarker } from '../../globals/promise';
|
||||||
|
import { isResolvedBlockOp, isResolvedSlotOp } from '../../utils';
|
||||||
|
import { pushAwaitedTask, shiftResolvedOp } from '../../runtime';
|
||||||
|
|
||||||
|
export const blockOp = (filters: BlockFilter[]) => {
|
||||||
|
let opMarkerValue = false;
|
||||||
|
|
||||||
|
const result = new Promise<[BlockFilter, boolean][] | null>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const { op, isOp } = shiftResolvedOp();
|
||||||
|
|
||||||
|
if (!isOp) {
|
||||||
|
pushAwaitedTask(constructBlock(filters));
|
||||||
|
opMarkerValue = true;
|
||||||
|
} else {
|
||||||
|
if (isResolvedSlotOp(op)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isResolvedBlockOp(op)) {
|
||||||
|
throw new Error('Block operation not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = extractBlock(op);
|
||||||
|
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
(result as Promise<[BlockFilter, boolean][] | null> & { [opMarker]: boolean })[opMarker] = opMarkerValue;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
@ -1,3 +1,5 @@
|
|||||||
export * from './block';
|
export * from './block';
|
||||||
|
export * from './rangeRead';
|
||||||
|
export * from './read';
|
||||||
export * from './store';
|
export * from './store';
|
||||||
export * from './take';
|
export * from './take';
|
||||||
44
packages/cwait/src/onchain/features/ops/rangeRead.ts
Normal file
44
packages/cwait/src/onchain/features/ops/rangeRead.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { Claim, ClaimKey, constructRangeRead, extractRead } from '@coinweb/contract-kit';
|
||||||
|
import { ClaimRange } from '@coinweb/contract-kit/dist/types/operations/read';
|
||||||
|
|
||||||
|
import { TypedClaim } from '../../../types';
|
||||||
|
import { context } from '../../context';
|
||||||
|
import { opMarker } from '../../globals/promise';
|
||||||
|
import { isResolvedReadOp, isResolvedSlotOp } from '../../utils';
|
||||||
|
import { pushAwaitedTask, shiftResolvedOp } from '../../runtime';
|
||||||
|
|
||||||
|
export const rangeReadOp = <TClaims extends Claim[] = TypedClaim[]>(
|
||||||
|
firstPart: ClaimKey['first_part'],
|
||||||
|
range: ClaimRange,
|
||||||
|
maxCount: number
|
||||||
|
) => {
|
||||||
|
let opMarkerValue = false;
|
||||||
|
|
||||||
|
const result = new Promise<TClaims | null>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const { op, isOp } = shiftResolvedOp();
|
||||||
|
|
||||||
|
if (!isOp) {
|
||||||
|
pushAwaitedTask(constructRangeRead(context.issuer, firstPart, range, maxCount));
|
||||||
|
opMarkerValue = true;
|
||||||
|
} else {
|
||||||
|
if (isResolvedSlotOp(op)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isResolvedReadOp(op)) {
|
||||||
|
throw new Error('Read operation not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const claim = (extractRead(op)?.map((result) => result.content) ?? null) as TClaims | null;
|
||||||
|
resolve(claim);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
(result as Promise<TClaims | null> & { [opMarker]: boolean })[opMarker] = opMarkerValue;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
43
packages/cwait/src/onchain/features/ops/read.ts
Normal file
43
packages/cwait/src/onchain/features/ops/read.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { Claim, ClaimKey, constructRead, extractRead } from '@coinweb/contract-kit';
|
||||||
|
|
||||||
|
import { TypedClaim } from '../../../types';
|
||||||
|
import { context } from '../../context';
|
||||||
|
import { opMarker } from '../../globals/promise';
|
||||||
|
import { pushAwaitedTask, shiftResolvedOp } from '../../runtime';
|
||||||
|
import { isResolvedReadOp, isResolvedSlotOp } from '../../utils';
|
||||||
|
|
||||||
|
export const readOp = <TClaim extends Claim = TypedClaim>(key: ClaimKey) => {
|
||||||
|
let opMarkerValue = false;
|
||||||
|
|
||||||
|
const result = new Promise<TClaim | null>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const { op, isOp } = shiftResolvedOp();
|
||||||
|
|
||||||
|
if (!isOp) {
|
||||||
|
pushAwaitedTask(constructRead(context.issuer, key));
|
||||||
|
opMarkerValue = true;
|
||||||
|
} else {
|
||||||
|
if (isResolvedSlotOp(op)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isResolvedReadOp(op)) {
|
||||||
|
console.log(JSON.stringify(op));
|
||||||
|
|
||||||
|
throw new Error('Read operation not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const claim = (extractRead(op)?.[0]?.content ?? null) as TClaim | null;
|
||||||
|
|
||||||
|
console.log('ResolveRead claim: ', claim);
|
||||||
|
resolve(claim);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
(result as Promise<TClaim | null> & { [opMarker]: boolean })[opMarker] = opMarkerValue;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
39
packages/cwait/src/onchain/features/ops/store.ts
Normal file
39
packages/cwait/src/onchain/features/ops/store.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Claim, constructStore } from '@coinweb/contract-kit';
|
||||||
|
import { extractStore } from '@coinweb/contract-kit/dist/esm/operations/store';
|
||||||
|
|
||||||
|
import { opMarker } from '../../globals/promise';
|
||||||
|
import { isResolvedSlotOp, isResolvedStoreOp } from '../../utils';
|
||||||
|
import { pushAwaitedTask, shiftResolvedOp } from '../../runtime';
|
||||||
|
|
||||||
|
export const storeOp = (claim: Claim, storeCweb?: bigint) => {
|
||||||
|
let opMarkerValue = false;
|
||||||
|
|
||||||
|
const result = new Promise<Claim | null>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const { op, isOp } = shiftResolvedOp();
|
||||||
|
|
||||||
|
if (!isOp) {
|
||||||
|
pushAwaitedTask({ ...constructStore(claim), providedCweb: storeCweb });
|
||||||
|
opMarkerValue = true;
|
||||||
|
} else {
|
||||||
|
if (isResolvedSlotOp(op)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isResolvedStoreOp(op)) {
|
||||||
|
throw new Error('Store operation not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = extractStore(op);
|
||||||
|
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
(result as Promise<Claim | null> & { [opMarker]: boolean })[opMarker] = opMarkerValue;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
38
packages/cwait/src/onchain/features/ops/take.ts
Normal file
38
packages/cwait/src/onchain/features/ops/take.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Claim, ClaimKey, constructTake, extractTake } from '@coinweb/contract-kit';
|
||||||
|
|
||||||
|
import { TypedClaim } from '../../../types';
|
||||||
|
import { opMarker } from '../../globals/promise';
|
||||||
|
import { isResolvedSlotOp, isResolvedTakeOp } from '../../utils';
|
||||||
|
import { pushAwaitedTask, shiftResolvedOp } from '../../runtime';
|
||||||
|
|
||||||
|
export const takeOp = <TClaim extends Claim = TypedClaim>(key: ClaimKey) => {
|
||||||
|
let opMarkerValue = false;
|
||||||
|
|
||||||
|
const result = new Promise<TClaim | null>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const { op, isOp } = shiftResolvedOp();
|
||||||
|
|
||||||
|
if (!isOp) {
|
||||||
|
pushAwaitedTask(constructTake(key));
|
||||||
|
opMarkerValue = true;
|
||||||
|
} else {
|
||||||
|
if (isResolvedSlotOp(op)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isResolvedTakeOp(op)) {
|
||||||
|
throw new Error('Take operation not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const claim = extractTake(op);
|
||||||
|
resolve(claim as TClaim | null);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
(result as Promise<TClaim | null> & { [opMarker]: boolean })[opMarker] = opMarkerValue;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
1
packages/cwait/src/onchain/globals/index.ts
Normal file
1
packages/cwait/src/onchain/globals/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './promise';
|
||||||
38
packages/cwait/src/onchain/globals/promise.ts
Normal file
38
packages/cwait/src/onchain/globals/promise.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { markTaskBatch } from '../runtime/awaitedTasks';
|
||||||
|
|
||||||
|
let batchId = 0;
|
||||||
|
|
||||||
|
const getBatchId = () => batchId++;
|
||||||
|
|
||||||
|
export const opMarker = Symbol('opMarker');
|
||||||
|
|
||||||
|
const wrapPromiseUtil =
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
<T extends (values: any) => any>(nativePromiseUtil: T): T =>
|
||||||
|
((values: Parameters<T>) => {
|
||||||
|
let argsWithMarker = 0;
|
||||||
|
values.forEach((arg) => {
|
||||||
|
if (arg instanceof Promise && (arg as Promise<unknown> & { [opMarker]: boolean })[opMarker]) {
|
||||||
|
argsWithMarker++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (argsWithMarker > 0) {
|
||||||
|
markTaskBatch(argsWithMarker, getBatchId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
|
return nativePromiseUtil(values);
|
||||||
|
}) as T;
|
||||||
|
|
||||||
|
const nativePromiseAll = Promise.all.bind(Promise);
|
||||||
|
const nativePromiseRace = Promise.race.bind(Promise);
|
||||||
|
const nativePromiseAny = Promise.any.bind(Promise);
|
||||||
|
const nativePromiseSettled = Promise.allSettled.bind(Promise);
|
||||||
|
const nativePromiseAllSettled = Promise.allSettled.bind(Promise);
|
||||||
|
|
||||||
|
globalThis.Promise.all = wrapPromiseUtil(nativePromiseAll);
|
||||||
|
globalThis.Promise.race = wrapPromiseUtil(nativePromiseRace);
|
||||||
|
globalThis.Promise.any = wrapPromiseUtil(nativePromiseAny);
|
||||||
|
globalThis.Promise.allSettled = wrapPromiseUtil(nativePromiseSettled);
|
||||||
|
globalThis.Promise.allSettled = wrapPromiseUtil(nativePromiseAllSettled);
|
||||||
@ -1,3 +1,3 @@
|
|||||||
export * from './context';
|
import './globals';
|
||||||
export * from './executor';
|
|
||||||
export * from './ops';
|
export * from './features';
|
||||||
|
|||||||
@ -0,0 +1,72 @@
|
|||||||
|
import { constructContractRef, ContractIssuer, CwebStore, GStore, GTake } from '@coinweb/contract-kit';
|
||||||
|
import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take';
|
||||||
|
|
||||||
|
import { PreparedExtendedStoreOp } from '../../../types';
|
||||||
|
import {
|
||||||
|
constructMutexExecOpsBlock,
|
||||||
|
constructMutexExecOpsClaimRead,
|
||||||
|
constructMutexLockClaimRangeRead,
|
||||||
|
} from '../claims';
|
||||||
|
import { execOpsMethodName } from '../methods';
|
||||||
|
import { MutexExecOpArgs } from '../types';
|
||||||
|
|
||||||
|
export const constructExecOpsCall = ({
|
||||||
|
issuer,
|
||||||
|
ops,
|
||||||
|
processId,
|
||||||
|
execId,
|
||||||
|
}: {
|
||||||
|
issuer: ContractIssuer;
|
||||||
|
ops: (GTake<CwebTake> | PreparedExtendedStoreOp)[];
|
||||||
|
processId: string;
|
||||||
|
execId?: string;
|
||||||
|
}) => {
|
||||||
|
const opsFee = BigInt(ops.length) * 100n;
|
||||||
|
const execCallInnerFee = 700n;
|
||||||
|
|
||||||
|
const takeOpsAdditionalFee = BigInt(ops.filter((op) => 'TakeOp' in op).length) * 100n;
|
||||||
|
const prepareTakeOpsInnerFee = takeOpsAdditionalFee > 0n ? 700n : 0n;
|
||||||
|
|
||||||
|
const storedCweb = ops
|
||||||
|
.filter((op) => 'StoreOp' in op)
|
||||||
|
.reduce((total, op) => {
|
||||||
|
if (op.providedCweb) {
|
||||||
|
return total + op.providedCweb;
|
||||||
|
}
|
||||||
|
return total + BigInt(op.StoreOp.fees_stored);
|
||||||
|
}, 0n);
|
||||||
|
|
||||||
|
const callFee = 700n;
|
||||||
|
|
||||||
|
const providedCweb = opsFee + execCallInnerFee + takeOpsAdditionalFee + prepareTakeOpsInnerFee + storedCweb + callFee;
|
||||||
|
const contractArgsFee = 100n;
|
||||||
|
|
||||||
|
const preparedOps: (GTake<CwebTake> | GStore<CwebStore>)[] = ops.map((op) => {
|
||||||
|
if ('StoreOp' in op) {
|
||||||
|
const preparedStoreOp = { ...op };
|
||||||
|
delete preparedStoreOp.providedCweb;
|
||||||
|
return preparedStoreOp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return op;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: execOpsMethodName,
|
||||||
|
methodArgs: [preparedOps, processId, execId] satisfies MutexExecOpArgs,
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: [constructMutexLockClaimRangeRead(issuer)],
|
||||||
|
},
|
||||||
|
fee: providedCweb + contractArgsFee,
|
||||||
|
ops: execId
|
||||||
|
? ([constructMutexExecOpsBlock(execId, issuer), constructMutexExecOpsClaimRead(issuer, execId)] as const)
|
||||||
|
: [],
|
||||||
|
};
|
||||||
|
};
|
||||||
40
packages/cwait/src/onchain/mutex/calls/constructLockCall.ts
Normal file
40
packages/cwait/src/onchain/mutex/calls/constructLockCall.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { ContractIssuer, constructContractRef } from '@coinweb/contract-kit';
|
||||||
|
|
||||||
|
import { constructMutexBlockLockClaimTake, constructMutexLockBlock, constructMutexLockClaimStore } from '../claims';
|
||||||
|
import { lockMethodName } from '../methods';
|
||||||
|
import { lockFee } from '../settings';
|
||||||
|
import { LockedKey } from '../types';
|
||||||
|
|
||||||
|
export const constructLockCall = ({
|
||||||
|
issuer,
|
||||||
|
keys,
|
||||||
|
lockId,
|
||||||
|
timestamp,
|
||||||
|
processId,
|
||||||
|
}: {
|
||||||
|
issuer: ContractIssuer;
|
||||||
|
lockId: string;
|
||||||
|
timestamp: number;
|
||||||
|
keys: LockedKey[];
|
||||||
|
processId: string;
|
||||||
|
}) => {
|
||||||
|
return {
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: lockMethodName,
|
||||||
|
methodArgs: [],
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb: lockFee,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: [],
|
||||||
|
},
|
||||||
|
inThreadOps: [constructMutexLockBlock(lockId, issuer), constructMutexBlockLockClaimTake(lockId)] as const,
|
||||||
|
outThreadOps: [
|
||||||
|
constructMutexLockClaimStore({ fee: lockFee, keys, lockId, timestamp, processId, locked: false }),
|
||||||
|
] as const,
|
||||||
|
fee: lockFee * 2n + 200n,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { constructContractRef, ContractIssuer } from '@coinweb/contract-kit';
|
||||||
|
|
||||||
|
import { constructMutexBlockUnlockClaimTake, constructMutexUnlockBlock } from '../claims';
|
||||||
|
import { unlockMethodName } from '../methods';
|
||||||
|
import { unlockFee } from '../settings';
|
||||||
|
import { MutexUnlockArgs } from '../types';
|
||||||
|
|
||||||
|
export const constructUnlockCall = (issuer: ContractIssuer, ...[lockId, timestamp, notify]: MutexUnlockArgs) => {
|
||||||
|
return {
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: unlockMethodName,
|
||||||
|
methodArgs: [lockId, timestamp] satisfies MutexUnlockArgs,
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb: unlockFee,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: [],
|
||||||
|
},
|
||||||
|
ops: notify
|
||||||
|
? ([constructMutexUnlockBlock(lockId, issuer), constructMutexBlockUnlockClaimTake(lockId)] as const)
|
||||||
|
: [],
|
||||||
|
fee: unlockFee + (notify ? 1000n : 800n),
|
||||||
|
};
|
||||||
|
};
|
||||||
3
packages/cwait/src/onchain/mutex/calls/index.ts
Normal file
3
packages/cwait/src/onchain/mutex/calls/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './constructLockCall';
|
||||||
|
export * from './constructExecOpsCall';
|
||||||
|
export * from './constructUnlockCall';
|
||||||
4
packages/cwait/src/onchain/mutex/claims/index.ts
Normal file
4
packages/cwait/src/onchain/mutex/claims/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './mutexExecOps';
|
||||||
|
export * from './mutexBlockLock';
|
||||||
|
export * from './mutexBlockUnlock';
|
||||||
|
export * from './mutexLock';
|
||||||
38
packages/cwait/src/onchain/mutex/claims/mutexBlockLock.ts
Normal file
38
packages/cwait/src/onchain/mutex/claims/mutexBlockLock.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
BlockFilter,
|
||||||
|
constructBlock,
|
||||||
|
constructClaim,
|
||||||
|
constructClaimKey,
|
||||||
|
constructStore,
|
||||||
|
constructTake,
|
||||||
|
ContractIssuer,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
|
||||||
|
import { MutexNotifyLockState } from '../types';
|
||||||
|
|
||||||
|
export const mutexBlockLockKey = '_mutex_block_lock';
|
||||||
|
|
||||||
|
export const constructMutexBlockLockClaimKey = (lockId: string) => constructClaimKey([mutexBlockLockKey], [lockId]);
|
||||||
|
|
||||||
|
export const constructMutexBlockLockClaim = ({ lockId, timestamp }: MutexNotifyLockState) =>
|
||||||
|
constructClaim(constructMutexBlockLockClaimKey(lockId), { lockId, timestamp } satisfies MutexNotifyLockState, '0x0');
|
||||||
|
|
||||||
|
export const constructMutexBlockLockClaimStore = ({ lockId, timestamp }: MutexNotifyLockState) =>
|
||||||
|
constructStore(constructMutexBlockLockClaim({ lockId, timestamp }));
|
||||||
|
|
||||||
|
export const constructMutexBlockLockClaimTake = (lockId: string) =>
|
||||||
|
constructTake(constructMutexBlockLockClaimKey(lockId));
|
||||||
|
|
||||||
|
export const constructMutexLockBlockFilter = (lockId: string, issuer: ContractIssuer): BlockFilter => {
|
||||||
|
const { first_part: first, second_part: second } = constructMutexBlockLockClaimKey(lockId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
issuer,
|
||||||
|
first,
|
||||||
|
second,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const constructMutexLockBlock = (lockId: string, issuer: ContractIssuer) => {
|
||||||
|
return constructBlock([constructMutexLockBlockFilter(lockId, issuer)]);
|
||||||
|
};
|
||||||
39
packages/cwait/src/onchain/mutex/claims/mutexBlockUnlock.ts
Normal file
39
packages/cwait/src/onchain/mutex/claims/mutexBlockUnlock.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
BlockFilter,
|
||||||
|
constructBlock,
|
||||||
|
constructClaim,
|
||||||
|
constructClaimKey,
|
||||||
|
constructStore,
|
||||||
|
constructTake,
|
||||||
|
ContractIssuer,
|
||||||
|
CwebStore,
|
||||||
|
GStore,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
|
||||||
|
export const mutexBlockUnlockKey = '_mutex_block_unlock';
|
||||||
|
|
||||||
|
export const constructMutexBlockUnlockClaimKey = (uniqueId: string) =>
|
||||||
|
constructClaimKey([mutexBlockUnlockKey], [uniqueId]);
|
||||||
|
|
||||||
|
export const constructMutexBlockUnlockClaim = (uniqueId: string) =>
|
||||||
|
constructClaim(constructMutexBlockUnlockClaimKey(uniqueId), {}, '0x0');
|
||||||
|
|
||||||
|
export const constructMutexBlockUnlockClaimStore = (uniqueId: string): GStore<CwebStore> =>
|
||||||
|
constructStore(constructMutexBlockUnlockClaim(uniqueId));
|
||||||
|
|
||||||
|
export const constructMutexBlockUnlockClaimTake = (uniqueId: string) =>
|
||||||
|
constructTake(constructMutexBlockUnlockClaimKey(uniqueId));
|
||||||
|
|
||||||
|
export const constructMutexUnlockBlockFilter = (lockId: string, issuer: ContractIssuer): BlockFilter => {
|
||||||
|
const { first_part: first, second_part: second } = constructMutexBlockUnlockClaimKey(lockId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
issuer,
|
||||||
|
first,
|
||||||
|
second,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const constructMutexUnlockBlock = (lockId: string, issuer: ContractIssuer) => {
|
||||||
|
return constructBlock([constructMutexUnlockBlockFilter(lockId, issuer)]);
|
||||||
|
};
|
||||||
47
packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts
Normal file
47
packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import {
|
||||||
|
BlockFilter,
|
||||||
|
constructBlock,
|
||||||
|
constructClaim,
|
||||||
|
constructClaimKey,
|
||||||
|
constructRead,
|
||||||
|
constructStore,
|
||||||
|
constructTake,
|
||||||
|
ContractIssuer,
|
||||||
|
CwebStore,
|
||||||
|
GStore,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
import { toHex } from 'lib/shared';
|
||||||
|
|
||||||
|
import { MutexExecOpsResult } from '../types';
|
||||||
|
|
||||||
|
export const mutexExecOpsKey = '_mutex_exec_ops';
|
||||||
|
|
||||||
|
export const constructMutexExecOpsClaimKey = (execId: string) => constructClaimKey([mutexExecOpsKey], [execId]);
|
||||||
|
|
||||||
|
export const constructMutexExecOpsClaim = (execId: string, result: MutexExecOpsResult, storeCweb: bigint) =>
|
||||||
|
constructClaim(constructMutexExecOpsClaimKey(execId), result, toHex(storeCweb));
|
||||||
|
|
||||||
|
export const constructMutexExecOpsClaimStore = (
|
||||||
|
execId: string,
|
||||||
|
result: MutexExecOpsResult,
|
||||||
|
storeCweb: bigint = 0n
|
||||||
|
): GStore<CwebStore> => constructStore(constructMutexExecOpsClaim(execId, result, storeCweb));
|
||||||
|
|
||||||
|
export const constructMutexExecOpsClaimRead = (issuer: ContractIssuer, execId: string) =>
|
||||||
|
constructRead(issuer, constructMutexExecOpsClaimKey(execId));
|
||||||
|
|
||||||
|
export const constructMutexExecOpsClaimTake = (execId: string) => constructTake(constructMutexExecOpsClaimKey(execId));
|
||||||
|
|
||||||
|
export const constructMutexExecOpsFilter = (lockId: string, issuer: ContractIssuer): BlockFilter => {
|
||||||
|
const { first_part: first, second_part: second } = constructMutexExecOpsClaimKey(lockId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
issuer,
|
||||||
|
first,
|
||||||
|
second,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const constructMutexExecOpsBlock = (lockId: string, issuer: ContractIssuer) => {
|
||||||
|
return constructBlock([constructMutexExecOpsFilter(lockId, issuer)]);
|
||||||
|
};
|
||||||
63
packages/cwait/src/onchain/mutex/claims/mutexLock.ts
Normal file
63
packages/cwait/src/onchain/mutex/claims/mutexLock.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import {
|
||||||
|
constructClaim,
|
||||||
|
constructClaimKey,
|
||||||
|
constructRangeRead,
|
||||||
|
constructStore,
|
||||||
|
ContractIssuer,
|
||||||
|
GRead,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
import { CwebRead } from '@coinweb/contract-kit/dist/types/operations/read';
|
||||||
|
import { toHex } from 'lib/shared';
|
||||||
|
|
||||||
|
import { rangeReadLimit } from '../settings';
|
||||||
|
import { LockedKey, MutexLockState } from '../types';
|
||||||
|
|
||||||
|
export const mutexLockKey = '_mutex_lock';
|
||||||
|
|
||||||
|
export const constructMutexLockFirstPart = () => [mutexLockKey];
|
||||||
|
|
||||||
|
export const constructMutexLockClaimKey = (lockId: string, timestamp: number) =>
|
||||||
|
constructClaimKey(constructMutexLockFirstPart(), [timestamp, lockId]);
|
||||||
|
|
||||||
|
export const constructMutexLockClaim = ({
|
||||||
|
fee,
|
||||||
|
keys,
|
||||||
|
locked,
|
||||||
|
lockId,
|
||||||
|
timestamp,
|
||||||
|
processId,
|
||||||
|
}: {
|
||||||
|
lockId: string;
|
||||||
|
timestamp: number;
|
||||||
|
locked: boolean;
|
||||||
|
keys: LockedKey[];
|
||||||
|
fee: bigint;
|
||||||
|
processId: string;
|
||||||
|
}) =>
|
||||||
|
constructClaim(
|
||||||
|
constructMutexLockClaimKey(lockId, timestamp),
|
||||||
|
{ locked, keys, processId } satisfies MutexLockState,
|
||||||
|
toHex(fee)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const constructMutexLockClaimStore = ({
|
||||||
|
fee,
|
||||||
|
keys,
|
||||||
|
lockId,
|
||||||
|
timestamp,
|
||||||
|
processId,
|
||||||
|
locked,
|
||||||
|
}: {
|
||||||
|
fee: bigint;
|
||||||
|
keys: LockedKey[];
|
||||||
|
lockId: string;
|
||||||
|
timestamp: number;
|
||||||
|
processId: string;
|
||||||
|
locked: boolean;
|
||||||
|
}) => constructStore(constructMutexLockClaim({ fee, keys, locked, lockId, timestamp, processId }));
|
||||||
|
|
||||||
|
export const constructMutexLockClaimRangeRead = (issuer: ContractIssuer): GRead<CwebRead> =>
|
||||||
|
constructRangeRead(issuer, constructMutexLockFirstPart(), {}, rangeReadLimit);
|
||||||
|
|
||||||
|
// export const constructMutexLockClaimTake = (lockId: string, timestamp: number) =>
|
||||||
|
// constructTake(constructMutexLockClaimKey(lockId, timestamp));
|
||||||
33
packages/cwait/src/onchain/mutex/index.ts
Normal file
33
packages/cwait/src/onchain/mutex/index.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
execLockMethodName,
|
||||||
|
execOps,
|
||||||
|
execOpsMethodName,
|
||||||
|
lockMethodName,
|
||||||
|
mutexExecLock,
|
||||||
|
mutexLock,
|
||||||
|
mutexUnlock,
|
||||||
|
notifyLock,
|
||||||
|
notifyLockMethodName,
|
||||||
|
preReadExecTakeOpsMethodName,
|
||||||
|
saveExecOpResult,
|
||||||
|
saveExecOpResultMethodName,
|
||||||
|
unlockMethodName,
|
||||||
|
waitMethodName,
|
||||||
|
} from './methods';
|
||||||
|
import { preReadExecTakeOps } from './methods/preReadExecTakeOps';
|
||||||
|
import { wait } from './methods/wait';
|
||||||
|
|
||||||
|
export * from './claims';
|
||||||
|
export * from './types';
|
||||||
|
export * from './calls';
|
||||||
|
|
||||||
|
export const mutexMethods = {
|
||||||
|
[execLockMethodName]: mutexExecLock,
|
||||||
|
[lockMethodName]: mutexLock,
|
||||||
|
[unlockMethodName]: mutexUnlock,
|
||||||
|
[execOpsMethodName]: execOps,
|
||||||
|
[preReadExecTakeOpsMethodName]: preReadExecTakeOps,
|
||||||
|
[saveExecOpResultMethodName]: saveExecOpResult,
|
||||||
|
[waitMethodName]: wait,
|
||||||
|
[notifyLockMethodName]: notifyLock,
|
||||||
|
};
|
||||||
81
packages/cwait/src/onchain/mutex/methods/execLock.ts
Normal file
81
packages/cwait/src/onchain/mutex/methods/execLock.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import {
|
||||||
|
constructClaim,
|
||||||
|
constructContinueTx,
|
||||||
|
constructContractRef,
|
||||||
|
constructStore,
|
||||||
|
constructTake,
|
||||||
|
Context,
|
||||||
|
extractContractArgs,
|
||||||
|
extractRead,
|
||||||
|
passCwebFrom,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
import { getCallParameters, getContractIssuer } from 'lib/onchain';
|
||||||
|
import { toHex, TypedClaim } from 'lib/shared';
|
||||||
|
|
||||||
|
import { lockFee } from '../settings';
|
||||||
|
import { MutexLockState, MutexWaitNotifyArgs } from '../types';
|
||||||
|
import { isMatchLockKeys } from '../utils';
|
||||||
|
|
||||||
|
import { waitMethodName } from './names';
|
||||||
|
|
||||||
|
export const mutexExecLock = (context: Context) => {
|
||||||
|
const { availableCweb } = getCallParameters(context);
|
||||||
|
const issuer = getContractIssuer(context);
|
||||||
|
|
||||||
|
const lockQueue = extractRead(extractContractArgs(context.tx)[0])?.map(
|
||||||
|
({ content }) => content as TypedClaim<MutexLockState>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!lockQueue) {
|
||||||
|
throw new Error('No lock queue found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const alreadyLockedKeys = lockQueue.filter(({ body }) => body.locked).map(({ key }) => key);
|
||||||
|
|
||||||
|
const lockCandidate = lockQueue.find(
|
||||||
|
({ body, key }) => !body.locked && !alreadyLockedKeys.some((lockedKey) => isMatchLockKeys(lockedKey, key))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!lockCandidate) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
constructContinueTx(
|
||||||
|
context,
|
||||||
|
[
|
||||||
|
passCwebFrom(issuer, availableCweb),
|
||||||
|
constructTake(lockCandidate.key),
|
||||||
|
constructStore(
|
||||||
|
constructClaim(
|
||||||
|
lockCandidate.key,
|
||||||
|
{ ...lockCandidate.body, locked: true },
|
||||||
|
toHex(BigInt(lockCandidate.fees_stored) - lockFee)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: waitMethodName,
|
||||||
|
methodArgs: [
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
timestamp: (lockCandidate.key.second_part as [number, string])[0],
|
||||||
|
lockId: (lockCandidate.key.second_part as [number, string])[1],
|
||||||
|
},
|
||||||
|
] satisfies MutexWaitNotifyArgs,
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb: availableCweb + BigInt(lockCandidate.fees_stored) - 1000n,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
};
|
||||||
121
packages/cwait/src/onchain/mutex/methods/execOps.ts
Normal file
121
packages/cwait/src/onchain/mutex/methods/execOps.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import {
|
||||||
|
constructContinueTx,
|
||||||
|
constructContractRef,
|
||||||
|
constructRead,
|
||||||
|
Context,
|
||||||
|
extractContractArgs,
|
||||||
|
extractRead,
|
||||||
|
PreparedOperation,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
import { getCallParameters, getContractIssuer, getMethodArguments } from 'lib/onchain';
|
||||||
|
import { TypedClaim } from 'lib/shared';
|
||||||
|
|
||||||
|
import { constructMutexExecOpsClaimStore } from '../claims';
|
||||||
|
import { MutexLockState, MutexExecOpArgs, MutexSaveExecOpResultArgs, MutexPreReadTakeOpsArgs } from '../types';
|
||||||
|
import { isMatchLockKeys } from '../utils';
|
||||||
|
|
||||||
|
import { saveExecOpResultMethodName } from './names';
|
||||||
|
|
||||||
|
export const execOps = (context: Context) => {
|
||||||
|
const { availableCweb } = getCallParameters(context);
|
||||||
|
const issuer = getContractIssuer(context);
|
||||||
|
|
||||||
|
const [ops, processId, execId] = getMethodArguments<MutexExecOpArgs>(context);
|
||||||
|
|
||||||
|
const lockQueue = extractRead(extractContractArgs(context.tx)[0])?.map(
|
||||||
|
({ content }) => content as TypedClaim<MutexLockState>
|
||||||
|
);
|
||||||
|
|
||||||
|
let availableOps: PreparedOperation[] = [];
|
||||||
|
const unavailableIndexes: number[] = [];
|
||||||
|
|
||||||
|
console.log('extractContractArgs(context.tx) >>>', JSON.stringify(extractContractArgs(context.tx)));
|
||||||
|
console.log('ops >>>', JSON.stringify(ops));
|
||||||
|
console.log('lockQueue >>>', JSON.stringify(lockQueue));
|
||||||
|
|
||||||
|
if (!lockQueue) {
|
||||||
|
availableOps = ops;
|
||||||
|
} else {
|
||||||
|
ops.forEach((op, i) => {
|
||||||
|
const claimKey = 'StoreOp' in op ? op.StoreOp.key : op.TakeOp.key;
|
||||||
|
const isLockedByOtherProcess = lockQueue.some(
|
||||||
|
({ body }) =>
|
||||||
|
body.locked && body.processId !== processId && body.keys.some((key) => isMatchLockKeys(key, claimKey))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isLockedByOtherProcess) {
|
||||||
|
unavailableIndexes.push(i);
|
||||||
|
} else {
|
||||||
|
availableOps.push(op);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableOps.length === 0) {
|
||||||
|
return execId
|
||||||
|
? [
|
||||||
|
constructContinueTx(context, [
|
||||||
|
constructMutexExecOpsClaimStore(execId, new Array(ops.length).fill({ ok: false })),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!execId) {
|
||||||
|
return [constructContinueTx(context, availableOps)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const preReadTakeOps = availableOps.filter((op) => 'TakeOp' in op).map((op) => constructRead(issuer, op.TakeOp.key));
|
||||||
|
|
||||||
|
if (preReadTakeOps.length > 0) {
|
||||||
|
const fee = 700n + BigInt(preReadTakeOps.length) * 100n;
|
||||||
|
|
||||||
|
return [
|
||||||
|
constructContinueTx(
|
||||||
|
context,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: saveExecOpResultMethodName,
|
||||||
|
methodArgs: [execId, unavailableIndexes, availableOps] satisfies MutexPreReadTakeOpsArgs,
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb: availableCweb - fee,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: preReadTakeOps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const fee = 700n + BigInt(availableOps.length) * 100n;
|
||||||
|
|
||||||
|
return [
|
||||||
|
constructContinueTx(
|
||||||
|
context,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: saveExecOpResultMethodName,
|
||||||
|
methodArgs: [execId, unavailableIndexes] satisfies MutexSaveExecOpResultArgs,
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb: availableCweb - fee,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: availableOps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
};
|
||||||
7
packages/cwait/src/onchain/mutex/methods/index.ts
Normal file
7
packages/cwait/src/onchain/mutex/methods/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export * from './execLock';
|
||||||
|
export * from './lock';
|
||||||
|
export * from './names';
|
||||||
|
export * from './notifyLock';
|
||||||
|
export * from './execOps';
|
||||||
|
export * from './unlock';
|
||||||
|
export * from './saveExecOpResult';
|
||||||
34
packages/cwait/src/onchain/mutex/methods/lock.ts
Normal file
34
packages/cwait/src/onchain/mutex/methods/lock.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { constructContinueTx, constructContractRef, Context } from '@coinweb/contract-kit';
|
||||||
|
import { getCallParameters, getContractIssuer } from 'lib/onchain';
|
||||||
|
|
||||||
|
import { constructMutexLockClaimRangeRead } from '../claims';
|
||||||
|
|
||||||
|
import { execLockMethodName } from './names';
|
||||||
|
|
||||||
|
export const mutexLock = (context: Context) => {
|
||||||
|
const { availableCweb } = getCallParameters(context);
|
||||||
|
const issuer = getContractIssuer(context);
|
||||||
|
|
||||||
|
return [
|
||||||
|
constructContinueTx(
|
||||||
|
context,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: execLockMethodName,
|
||||||
|
methodArgs: [],
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb: availableCweb - 700n,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: [constructMutexLockClaimRangeRead(issuer)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
};
|
||||||
8
packages/cwait/src/onchain/mutex/methods/names.ts
Normal file
8
packages/cwait/src/onchain/mutex/methods/names.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export const lockMethodName = '_mutex_lock';
|
||||||
|
export const execLockMethodName = '_mutex_execLock';
|
||||||
|
export const unlockMethodName = '_mutex_unlock';
|
||||||
|
export const notifyLockMethodName = '_mutex_notify_lock';
|
||||||
|
export const execOpsMethodName = '_mutex_execOps';
|
||||||
|
export const saveExecOpResultMethodName = '_mutex_save_exec_op_result';
|
||||||
|
export const preReadExecTakeOpsMethodName = '_mutex_pre_read_exec_take_ops';
|
||||||
|
export const waitMethodName = '_mutex_wait';
|
||||||
11
packages/cwait/src/onchain/mutex/methods/notifyLock.ts
Normal file
11
packages/cwait/src/onchain/mutex/methods/notifyLock.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { constructContinueTx, Context } from '@coinweb/contract-kit';
|
||||||
|
import { getMethodArguments } from 'lib/onchain';
|
||||||
|
|
||||||
|
import { constructMutexBlockLockClaimStore } from '../claims/mutexBlockLock';
|
||||||
|
import { MutexNotifyLockArgs } from '../types';
|
||||||
|
|
||||||
|
export const notifyLock = (context: Context) => {
|
||||||
|
const [lockInfo] = getMethodArguments<MutexNotifyLockArgs>(context);
|
||||||
|
|
||||||
|
return [constructContinueTx(context, [constructMutexBlockLockClaimStore(lockInfo)])];
|
||||||
|
};
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
import {
|
||||||
|
constructContinueTx,
|
||||||
|
constructContractRef,
|
||||||
|
Context,
|
||||||
|
extractContractArgs,
|
||||||
|
extractRead,
|
||||||
|
passCwebFrom,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
import { getCallParameters, getContractIssuer, getMethodArguments } from 'lib/onchain';
|
||||||
|
|
||||||
|
import { MutexPreReadTakeOpsArgs, MutexSaveExecOpResultArgs } from '../types';
|
||||||
|
|
||||||
|
import { saveExecOpResultMethodName } from './names';
|
||||||
|
|
||||||
|
export const preReadExecTakeOps = (context: Context) => {
|
||||||
|
const { availableCweb } = getCallParameters(context);
|
||||||
|
const issuer = getContractIssuer(context);
|
||||||
|
|
||||||
|
const [execId, unavailableIndexes, ops] = getMethodArguments<MutexPreReadTakeOpsArgs>(context);
|
||||||
|
|
||||||
|
const storedCweb = extractContractArgs(context.tx)
|
||||||
|
.map((op) => BigInt(extractRead(op)?.[0]?.content.fees_stored ?? 0))
|
||||||
|
.reduce((total, op) => total + op, 0n);
|
||||||
|
|
||||||
|
const fee = 700n + BigInt(ops.length) * 100n;
|
||||||
|
|
||||||
|
return [
|
||||||
|
constructContinueTx(
|
||||||
|
context,
|
||||||
|
[passCwebFrom(issuer, availableCweb)],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: saveExecOpResultMethodName,
|
||||||
|
methodArgs: [execId, unavailableIndexes] satisfies MutexSaveExecOpResultArgs,
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb: availableCweb + storedCweb - fee,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: ops,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
};
|
||||||
33
packages/cwait/src/onchain/mutex/methods/saveExecOpResult.ts
Normal file
33
packages/cwait/src/onchain/mutex/methods/saveExecOpResult.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { constructContinueTx, Context, extractContractArgs } from '@coinweb/contract-kit';
|
||||||
|
import { getCallParameters, getMethodArguments } from 'lib/onchain';
|
||||||
|
|
||||||
|
import { constructMutexExecOpsClaimStore } from '../claims';
|
||||||
|
import { MutexExecOpsResult, MutexSaveExecOpResultArgs } from '../types';
|
||||||
|
|
||||||
|
export const saveExecOpResult = (context: Context) => {
|
||||||
|
const { availableCweb } = getCallParameters(context);
|
||||||
|
|
||||||
|
const [execId, unavailableIndexes] = getMethodArguments<MutexSaveExecOpResultArgs>(context);
|
||||||
|
const resolvedOps = extractContractArgs(context.tx);
|
||||||
|
|
||||||
|
const result = new Array(resolvedOps.length + unavailableIndexes.length)
|
||||||
|
.fill({ ok: false, error: 'Resource is busy' })
|
||||||
|
.map((value: MutexExecOpsResult[number], i) => {
|
||||||
|
if (unavailableIndexes.includes(i)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedOp = resolvedOps.shift();
|
||||||
|
|
||||||
|
if (!resolvedOp) {
|
||||||
|
throw new Error('An error occurred while saving the exec op result');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: true as const,
|
||||||
|
resolved: resolvedOp,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return [constructContinueTx(context, [constructMutexExecOpsClaimStore(execId, result, availableCweb - 200n)])];
|
||||||
|
};
|
||||||
42
packages/cwait/src/onchain/mutex/methods/unlock.ts
Normal file
42
packages/cwait/src/onchain/mutex/methods/unlock.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { constructContinueTx, constructContractRef, constructTake, Context, passCwebFrom } from '@coinweb/contract-kit';
|
||||||
|
import { getCallParameters, getContractIssuer, getMethodArguments } from 'lib/onchain';
|
||||||
|
|
||||||
|
import { constructMutexBlockUnlockClaimStore, constructMutexLockClaimKey } from '../claims';
|
||||||
|
import { lockFee } from '../settings';
|
||||||
|
import { MutexUnlockArgs } from '../types';
|
||||||
|
|
||||||
|
import { lockMethodName } from './names';
|
||||||
|
|
||||||
|
export const mutexUnlock = (context: Context) => {
|
||||||
|
const { availableCweb } = getCallParameters(context);
|
||||||
|
const issuer = getContractIssuer(context);
|
||||||
|
|
||||||
|
const [lockId, timestamp] = getMethodArguments<MutexUnlockArgs>(context);
|
||||||
|
|
||||||
|
return [
|
||||||
|
constructContinueTx(
|
||||||
|
context,
|
||||||
|
[
|
||||||
|
passCwebFrom(issuer, availableCweb),
|
||||||
|
constructTake(constructMutexLockClaimKey(lockId, timestamp)),
|
||||||
|
constructMutexBlockUnlockClaimStore(lockId),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: lockMethodName,
|
||||||
|
methodArgs: [],
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb: lockFee,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
};
|
||||||
62
packages/cwait/src/onchain/mutex/methods/wait.ts
Normal file
62
packages/cwait/src/onchain/mutex/methods/wait.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { constructContinueTx, constructContractRef, Context } from '@coinweb/contract-kit';
|
||||||
|
import { getCallParameters, getContractIssuer, getMethodArguments } from 'lib/onchain';
|
||||||
|
|
||||||
|
import { waitSteps } from '../settings';
|
||||||
|
import { MutexNotifyLockArgs, MutexWaitNotifyArgs } from '../types';
|
||||||
|
|
||||||
|
import { notifyLockMethodName, waitMethodName } from './names';
|
||||||
|
|
||||||
|
export const wait = (context: Context) => {
|
||||||
|
const issuer = getContractIssuer(context);
|
||||||
|
const [step, lockInfo] = getMethodArguments<MutexWaitNotifyArgs>(context);
|
||||||
|
const { availableCweb } = getCallParameters(context);
|
||||||
|
|
||||||
|
//Including the notifyLock method as step
|
||||||
|
if (step + 1 < waitSteps) {
|
||||||
|
return [
|
||||||
|
constructContinueTx(
|
||||||
|
context,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: waitMethodName,
|
||||||
|
methodArgs: [step + 1, lockInfo] satisfies MutexWaitNotifyArgs,
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb: availableCweb - 800n,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
constructContinueTx(
|
||||||
|
context,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
callInfo: {
|
||||||
|
ref: constructContractRef(issuer, []),
|
||||||
|
methodInfo: {
|
||||||
|
methodName: notifyLockMethodName,
|
||||||
|
methodArgs: [lockInfo] satisfies MutexNotifyLockArgs,
|
||||||
|
},
|
||||||
|
contractInfo: {
|
||||||
|
providedCweb: availableCweb - 800n,
|
||||||
|
authenticated: null,
|
||||||
|
},
|
||||||
|
contractArgs: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
};
|
||||||
6
packages/cwait/src/onchain/mutex/settings.ts
Normal file
6
packages/cwait/src/onchain/mutex/settings.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export const lockFee = 5000n;
|
||||||
|
export const unlockFee = 1000n + lockFee;
|
||||||
|
|
||||||
|
export const rangeReadLimit = 10000;
|
||||||
|
|
||||||
|
export const waitSteps = 2;
|
||||||
38
packages/cwait/src/onchain/mutex/types.ts
Normal file
38
packages/cwait/src/onchain/mutex/types.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { ClaimKey, CwebStore, GStore, GTake, PreparedOperation, ResolvedOperation } from '@coinweb/contract-kit';
|
||||||
|
import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take';
|
||||||
|
|
||||||
|
export type LockedKey = Omit<ClaimKey, 'second_part'> & Partial<Pick<ClaimKey, 'second_part'>>;
|
||||||
|
|
||||||
|
export type MutexLockState = {
|
||||||
|
locked: boolean;
|
||||||
|
keys: LockedKey[];
|
||||||
|
processId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MutexNotifyLockState = {
|
||||||
|
timestamp: number;
|
||||||
|
lockId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MutexExecOpsResult = (
|
||||||
|
| {
|
||||||
|
ok: true;
|
||||||
|
resolved: ResolvedOperation;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
ok: false;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
)[];
|
||||||
|
|
||||||
|
export type MutexUnlockArgs = [lockId: string, timestamp: number, notify?: boolean];
|
||||||
|
|
||||||
|
export type MutexExecOpArgs = [ops: (GTake<CwebTake> | GStore<CwebStore>)[], processId: string, execId?: string];
|
||||||
|
|
||||||
|
export type MutexWaitNotifyArgs = [step: number, lockInfo: MutexNotifyLockState];
|
||||||
|
|
||||||
|
export type MutexNotifyLockArgs = [lockInfo: MutexNotifyLockState];
|
||||||
|
|
||||||
|
export type MutexPreReadTakeOpsArgs = [execId: string, unavailableIndexes: number[], ops: PreparedOperation[]];
|
||||||
|
|
||||||
|
export type MutexSaveExecOpResultArgs = [execId: string, unavailableIndexes: number[]];
|
||||||
1
packages/cwait/src/onchain/mutex/utils/index.ts
Normal file
1
packages/cwait/src/onchain/mutex/utils/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './isMatchLockKeys';
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { LockedKey } from '../types';
|
||||||
|
|
||||||
|
export const isMatchLockKeys = (lockKey1: LockedKey, lockKey2: LockedKey) => {
|
||||||
|
if (!lockKey1.second_part || !lockKey2.second_part) {
|
||||||
|
return lockKey1.first_part === lockKey2.first_part;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lockKey1.first_part === lockKey2.first_part && lockKey1.second_part === lockKey2.second_part;
|
||||||
|
};
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import { PreparedOperation } from '@coinweb/contract-kit';
|
|
||||||
|
|
||||||
const awaitedOps: PreparedOperation[] = [];
|
|
||||||
|
|
||||||
export const pushAwaitedOp = (op: PreparedOperation | PreparedOperation[]) => {
|
|
||||||
if (Array.isArray(op)) {
|
|
||||||
awaitedOps.push(...op);
|
|
||||||
} else {
|
|
||||||
awaitedOps.push(op);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAwaitedOps = () => awaitedOps;
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
export const blockOp = () => {
|
|
||||||
return new Promise<void>((resolve) => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import { Claim, ClaimKey, constructRead, extractRead, isResolvedRead } from '@coinweb/contract-kit';
|
|
||||||
|
|
||||||
import { TypedClaim } from '../../types';
|
|
||||||
import { context } from '../context';
|
|
||||||
import { abort } from '../executor';
|
|
||||||
|
|
||||||
import { pushAwaitedOp } from './awaited';
|
|
||||||
import { shiftResolvedOp } from './resolved';
|
|
||||||
|
|
||||||
export const readOp = <TClaim extends Claim = TypedClaim>(key: ClaimKey) => {
|
|
||||||
const { op, isOp } = shiftResolvedOp();
|
|
||||||
|
|
||||||
if (!isOp) {
|
|
||||||
pushAwaitedOp(constructRead(context.issuer, key));
|
|
||||||
} else if (op && !isResolvedRead(op)) {
|
|
||||||
throw new Error('Read operation not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<TClaim | null>((resolve) => {
|
|
||||||
if (isOp) {
|
|
||||||
const claim = op && ((extractRead(op)?.[0] ?? null) as TClaim | null);
|
|
||||||
resolve(claim);
|
|
||||||
} else {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import { ResolvedOperation } from '@coinweb/contract-kit';
|
|
||||||
|
|
||||||
const resolvedOps: ResolvedOperation[] = [];
|
|
||||||
|
|
||||||
export const pushResolvedOp = (op: ResolvedOperation | ResolvedOperation[]) => {
|
|
||||||
if (Array.isArray(op)) {
|
|
||||||
resolvedOps.push(...op);
|
|
||||||
} else {
|
|
||||||
resolvedOps.push(op);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const shiftResolvedOp = () =>
|
|
||||||
({
|
|
||||||
isOp: resolvedOps.length > 0,
|
|
||||||
op: resolvedOps.shift(),
|
|
||||||
}) as
|
|
||||||
| {
|
|
||||||
isOp: true;
|
|
||||||
op: ResolvedOperation | null;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
isOp: false;
|
|
||||||
op: undefined;
|
|
||||||
};
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { Claim, constructStore, isResolvedStore } from '@coinweb/contract-kit';
|
|
||||||
import { extractStore } from '@coinweb/contract-kit/dist/esm/operations/store';
|
|
||||||
|
|
||||||
import { abort } from '../executor';
|
|
||||||
|
|
||||||
import { pushAwaitedOp } from './awaited';
|
|
||||||
import { shiftResolvedOp } from './resolved';
|
|
||||||
|
|
||||||
export const storeOp = (claim: Claim) => {
|
|
||||||
const { op, isOp } = shiftResolvedOp();
|
|
||||||
|
|
||||||
if (!isOp) {
|
|
||||||
pushAwaitedOp(constructStore(claim));
|
|
||||||
} else if (op && !isResolvedStore(op)) {
|
|
||||||
throw new Error('Store operation not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = op && extractStore(op);
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
throw new Error('Wrong store operation');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<Claim>((resolve) => {
|
|
||||||
if (isOp) {
|
|
||||||
resolve(result);
|
|
||||||
} else {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
import { constructTake, extractTake, isResolvedTake, Claim, ClaimKey } from '@coinweb/contract-kit';
|
|
||||||
|
|
||||||
import { TypedClaim } from '../../types';
|
|
||||||
import { abort } from '../executor';
|
|
||||||
|
|
||||||
import { pushAwaitedOp } from './awaited';
|
|
||||||
import { shiftResolvedOp } from './resolved';
|
|
||||||
|
|
||||||
export const takeOp = <TClaim extends Claim = TypedClaim>(key: ClaimKey) => {
|
|
||||||
const { op, isOp } = shiftResolvedOp();
|
|
||||||
|
|
||||||
if (!isOp) {
|
|
||||||
pushAwaitedOp(constructTake(key));
|
|
||||||
} else if (op && !isResolvedTake(op)) {
|
|
||||||
throw new Error('Take operation not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<TClaim | null>((resolve) => {
|
|
||||||
if (isOp) {
|
|
||||||
const claim = op && extractTake(op);
|
|
||||||
resolve(claim as TClaim | null);
|
|
||||||
} else {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
17
packages/cwait/src/onchain/runtime/awaitedTasks.ts
Normal file
17
packages/cwait/src/onchain/runtime/awaitedTasks.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { PreparedOp, Task } from '../../types';
|
||||||
|
|
||||||
|
const awaitedTasks: Task[] = [];
|
||||||
|
|
||||||
|
export const pushAwaitedTask = (op: PreparedOp) => {
|
||||||
|
awaitedTasks.push({ op, batchId: -1 });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAwaitedTasks = () => [...awaitedTasks];
|
||||||
|
|
||||||
|
export const markTaskBatch = (count: number, batchId: number) => {
|
||||||
|
for (let i = 1; i <= count; i++) {
|
||||||
|
awaitedTasks.at(i * -1)!.batchId = batchId;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAwaitedTasksCount = () => awaitedTasks.length;
|
||||||
54
packages/cwait/src/onchain/runtime/execLoop.ts
Normal file
54
packages/cwait/src/onchain/runtime/execLoop.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { ResolvedOp } from '../../types';
|
||||||
|
|
||||||
|
import { pushResolvedOp } from './resolvedOps';
|
||||||
|
|
||||||
|
let abortExecution: ((isFullyExecuted: boolean) => void) | null = null;
|
||||||
|
|
||||||
|
export const stopExecution = (isFullyExecuted = false) => {
|
||||||
|
console.log('stopExecution');
|
||||||
|
abortExecution?.(isFullyExecuted);
|
||||||
|
};
|
||||||
|
|
||||||
|
type ExecTask = () => Promise<unknown>;
|
||||||
|
type Exec = { task: ExecTask; ops: ResolvedOp[] };
|
||||||
|
|
||||||
|
const execQueue: Exec[] = [];
|
||||||
|
export const setNextExec = (task: ExecTask, ops: ResolvedOp[]) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
execQueue.push({
|
||||||
|
task: () => task().then(resolve, reject),
|
||||||
|
ops,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const execLoop = async (): Promise<boolean> => {
|
||||||
|
const nextExec = execQueue.pop();
|
||||||
|
|
||||||
|
if (nextExec) {
|
||||||
|
const execution = new Promise<boolean>((resolve, reject) => {
|
||||||
|
abortExecution = resolve;
|
||||||
|
|
||||||
|
pushResolvedOp(nextExec.ops);
|
||||||
|
nextExec.task().then(
|
||||||
|
() => {
|
||||||
|
resolve(true);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.log(error);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const isFullyExecuted = await execution;
|
||||||
|
|
||||||
|
if (isFullyExecuted) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return execLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
3
packages/cwait/src/onchain/runtime/index.ts
Normal file
3
packages/cwait/src/onchain/runtime/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './awaitedTasks';
|
||||||
|
export * from './execLoop';
|
||||||
|
export * from './resolvedOps';
|
||||||
61
packages/cwait/src/onchain/runtime/resolvedOps.ts
Normal file
61
packages/cwait/src/onchain/runtime/resolvedOps.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { ResolvedOp } from '../../types';
|
||||||
|
|
||||||
|
const resolvedOps: ResolvedOp[] = [];
|
||||||
|
|
||||||
|
let usedOps: ResolvedOp[] = [];
|
||||||
|
let isSavingUsed = false;
|
||||||
|
let isFreezed = false;
|
||||||
|
|
||||||
|
export const pushResolvedOp = (op: ResolvedOp | ResolvedOp[]) => {
|
||||||
|
if (isFreezed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(op)) {
|
||||||
|
resolvedOps.push(...op);
|
||||||
|
} else {
|
||||||
|
resolvedOps.push(op);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shiftResolvedOp = () => {
|
||||||
|
const result = {
|
||||||
|
isOp: resolvedOps.length > 0 && !isFreezed,
|
||||||
|
op: isFreezed ? undefined : resolvedOps.shift(),
|
||||||
|
} as
|
||||||
|
| {
|
||||||
|
isOp: true;
|
||||||
|
op: ResolvedOp | null;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
isOp: false;
|
||||||
|
op: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isSavingUsed && result.op) {
|
||||||
|
usedOps.push(result.op);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('shiftResolvedOp: ', JSON.stringify(result));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUsedOps = () => usedOps;
|
||||||
|
|
||||||
|
export const startSavingUsedOps = () => {
|
||||||
|
usedOps = [];
|
||||||
|
isSavingUsed = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stopSavingUsedOps = () => {
|
||||||
|
isSavingUsed = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const freezeResolvedOps = () => {
|
||||||
|
isFreezed = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const unfreezeResolvedOps = () => {
|
||||||
|
isFreezed = false;
|
||||||
|
};
|
||||||
2
packages/cwait/src/onchain/utils/index.ts
Normal file
2
packages/cwait/src/onchain/utils/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './opTypeGuards';
|
||||||
|
export * from './uuid';
|
||||||
97
packages/cwait/src/onchain/utils/opTypeGuards.ts
Normal file
97
packages/cwait/src/onchain/utils/opTypeGuards.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import {
|
||||||
|
CwebStore,
|
||||||
|
isResolvedBlock,
|
||||||
|
isResolvedRead,
|
||||||
|
isResolvedStore,
|
||||||
|
isResolvedTake,
|
||||||
|
ResolvedOperation,
|
||||||
|
ResolvedRead,
|
||||||
|
ResolvedTake,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
import { CwebBlock } from '@coinweb/contract-kit/dist/esm/operations/block';
|
||||||
|
import { CwebCallRefResolved, isResolvedCall } from '@coinweb/contract-kit/dist/esm/operations/call';
|
||||||
|
import { ResolvedBlock } from '@coinweb/contract-kit/dist/types/operations/block';
|
||||||
|
import { GBlock, GCall, GRead, GStore, GTake } from '@coinweb/contract-kit/dist/types/operations/generics';
|
||||||
|
import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take';
|
||||||
|
|
||||||
|
import {
|
||||||
|
PreparedExecOp,
|
||||||
|
PreparedExtendedStoreOp,
|
||||||
|
PreparedLockOp,
|
||||||
|
PreparedOp,
|
||||||
|
PreparedUnlockOp,
|
||||||
|
ResolvedChildOp,
|
||||||
|
ResolvedExecOp,
|
||||||
|
ResolvedLockOp,
|
||||||
|
ResolvedOp,
|
||||||
|
ResolvedSlotOp,
|
||||||
|
ResolvedUnlockOp,
|
||||||
|
} from '../../types';
|
||||||
|
|
||||||
|
export const isResolvedSlotOp = (op?: ResolvedOp | null): op is ResolvedSlotOp => {
|
||||||
|
return !!(op && 'SlotOp' in op);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isResolvedExecOp = (op?: ResolvedOp | null): op is ResolvedExecOp => {
|
||||||
|
return !!(op && 'ExecOp' in op);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isResolvedChildOp = (op?: ResolvedOp | null): op is ResolvedChildOp => {
|
||||||
|
return !!(op && 'ChildOp' in op);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isResolvedLockOp = (op?: ResolvedOp | null): op is ResolvedLockOp => {
|
||||||
|
return !!(op && 'LockOp' in op);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isResolvedUnlockOp = (op?: ResolvedOp | null): op is ResolvedUnlockOp => {
|
||||||
|
return !!(op && 'UnlockOp' in op);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isResolvedBlockOp = (op?: ResolvedOp | null): op is GBlock<ResolvedBlock> => {
|
||||||
|
return isResolvedBlock(op as ResolvedOperation); //TODO: Fix contract-kit types
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isResolvedStoreOp = (op?: ResolvedOp | null): op is GStore<CwebStore> => {
|
||||||
|
return isResolvedStore(op as ResolvedOperation); //TODO: Fix contract-kit types
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isResolvedCallOp = (op?: ResolvedOp | null): op is GCall<CwebCallRefResolved> => {
|
||||||
|
return isResolvedCall(op as ResolvedOperation); //TODO: Fix contract-kit types
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isResolvedTakeOp = (op?: ResolvedOp | null): op is GTake<ResolvedTake> => {
|
||||||
|
return isResolvedTake(op as ResolvedOperation); //TODO: Fix contract-kit types
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isResolvedReadOp = (op?: ResolvedOp | null): op is GRead<ResolvedRead> => {
|
||||||
|
return isResolvedRead(op as ResolvedOperation); //TODO: Fix contract-kit types
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPreparedExecOp = (op?: PreparedOp | null): op is PreparedExecOp => {
|
||||||
|
return !!(op && 'ExecOp' in op);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPreparedBlockOp = (op?: PreparedOp | null): op is GBlock<CwebBlock> => {
|
||||||
|
return !!(op && 'BlockOp' in op);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPreparedLockOp = (op?: PreparedOp | null): op is PreparedLockOp => {
|
||||||
|
return !!(op && 'LockOp' in op);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPreparedUnlockOp = (op?: PreparedOp | null): op is PreparedUnlockOp => {
|
||||||
|
return !!(op && 'UnlockOp' in op);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPreparedStoreOp = (op?: PreparedOp | null): op is GStore<CwebStore> => {
|
||||||
|
return !!(op && 'StoreOp' in op);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPreparedExtendedStoreOp = (op?: PreparedOp | null): op is PreparedExtendedStoreOp => {
|
||||||
|
return !!(op && 'StoreOp' in op);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPreparedTakeOp = (op?: PreparedOp | null): op is GTake<CwebTake> => {
|
||||||
|
return !!(op && 'TakeOp' in op);
|
||||||
|
};
|
||||||
11
packages/cwait/src/onchain/utils/uuid.ts
Normal file
11
packages/cwait/src/onchain/utils/uuid.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { context } from '../context';
|
||||||
|
|
||||||
|
let next = 0n;
|
||||||
|
|
||||||
|
export const uuid = () => {
|
||||||
|
const id = (BigInt(`0x${context.thisId}`) * 3n + BigInt(`0x${context.parentTxId}`) + next).toString(16).slice(-32);
|
||||||
|
|
||||||
|
next += 1n;
|
||||||
|
|
||||||
|
return id;
|
||||||
|
};
|
||||||
@ -1,4 +1,15 @@
|
|||||||
import { Claim, ClaimKey, OrdJson } from '@coinweb/contract-kit';
|
import {
|
||||||
|
Claim,
|
||||||
|
ClaimKey,
|
||||||
|
CwebStore,
|
||||||
|
GStore,
|
||||||
|
OrdJson,
|
||||||
|
PreparedOperation,
|
||||||
|
ResolvedOperation,
|
||||||
|
User,
|
||||||
|
} from '@coinweb/contract-kit';
|
||||||
|
|
||||||
|
import { LockedKey } from './onchain/mutex';
|
||||||
|
|
||||||
export type HexBigInt = `0x${string}`;
|
export type HexBigInt = `0x${string}`;
|
||||||
|
|
||||||
@ -7,3 +18,91 @@ export type TypedClaim<TBody extends OrdJson = OrdJson, TKey extends ClaimKey =
|
|||||||
fees_stored: HexBigInt;
|
fees_stored: HexBigInt;
|
||||||
key: TKey;
|
key: TKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ResolvedSlotOp = {
|
||||||
|
SlotOp:
|
||||||
|
| {
|
||||||
|
ok: false;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
ok: true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResolvedExecOp = {
|
||||||
|
ExecOp: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResolvedChildOp = {
|
||||||
|
ChildOp: {
|
||||||
|
ops: ResolvedOp[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResolvedLockOp = {
|
||||||
|
LockOp: {
|
||||||
|
lockId: string;
|
||||||
|
timestamp: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResolvedUnlockOp = {
|
||||||
|
UnlockOp: 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResolvedOp =
|
||||||
|
| ResolvedOperation
|
||||||
|
| ResolvedSlotOp
|
||||||
|
| ResolvedExecOp
|
||||||
|
| ResolvedChildOp
|
||||||
|
| ResolvedLockOp
|
||||||
|
| ResolvedUnlockOp;
|
||||||
|
|
||||||
|
export type PreparedExecOp = {
|
||||||
|
ExecOp: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PreparedLockOp = {
|
||||||
|
LockOp: {
|
||||||
|
lockId: string;
|
||||||
|
keys: LockedKey[];
|
||||||
|
timestamp: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PreparedUnlockOp = {
|
||||||
|
UnlockOp: {
|
||||||
|
lockId: string;
|
||||||
|
timestamp: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PreparedExtendedStoreOp = GStore<CwebStore> & { providedCweb?: bigint };
|
||||||
|
|
||||||
|
export type PreparedOp =
|
||||||
|
| PreparedOperation
|
||||||
|
| PreparedExecOp
|
||||||
|
| PreparedLockOp
|
||||||
|
| PreparedUnlockOp
|
||||||
|
| PreparedExtendedStoreOp;
|
||||||
|
|
||||||
|
export type Task = {
|
||||||
|
op: PreparedOp;
|
||||||
|
batchId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ExecutorMethodArgs = [
|
||||||
|
initialArgs?: unknown[],
|
||||||
|
resolvedOps?: ResolvedOp[],
|
||||||
|
caller?: User,
|
||||||
|
thisId?: string,
|
||||||
|
parentId?: string,
|
||||||
|
shouldSaveResult?: boolean,
|
||||||
|
takenFundsIds?: string[],
|
||||||
|
execOpsIndexes?: number[],
|
||||||
|
];
|
||||||
|
|||||||
@ -9,5 +9,5 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"types": ["vitest/globals"]
|
"types": ["vitest/globals"]
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "vitest.*.ts", "__tests__", "scripts/publish.js"]
|
"include": ["**/*.ts", "vitest.*.ts", "__tests__", "scripts/publish.js", "src/onchain/utils", "src/onchain/executor/constructTx"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +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"
|
VITE_CONTRACT_ADDRESS="0xc599810e4861b7b1ae25695d58eeef10556fc84f87f72fafa31c0921e86e92be"
|
||||||
|
|||||||
@ -5,13 +5,13 @@ import { QRCodeSVG } from 'qrcode.react';
|
|||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const [word, setWord] = useState('');
|
const [word, setWord] = useState('');
|
||||||
const [qrCode, setQrCode] = useState('');
|
|
||||||
|
|
||||||
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setQrCode(constructAddWordUiCommand({ word, contractId: import.meta.env.VITE_CONTRACT_ADDRESS ?? '' }));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const qrCode = constructAddWordUiCommand({ word, contractId: import.meta.env.VITE_CONTRACT_ADDRESS ?? '' });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
@ -28,15 +28,10 @@ export const App = () => {
|
|||||||
value={word}
|
value={word}
|
||||||
onChange={(e) => setWord(e.target.value)}
|
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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div className="flex w-full justify-center py-4">
|
<div className="flex w-full justify-center py-4">
|
||||||
|
{word && (
|
||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(qrCode).catch((err) => {
|
navigator.clipboard.writeText(qrCode).catch((err) => {
|
||||||
@ -50,6 +45,7 @@ export const App = () => {
|
|||||||
bgColor="transparent"
|
bgColor="transparent"
|
||||||
fgColor="black"
|
fgColor="black"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,220 +0,0 @@
|
|||||||
import {
|
|
||||||
type GqlClaimFilter,
|
|
||||||
type GqlIssuedClaim,
|
|
||||||
type NetworkName,
|
|
||||||
NodeConnection,
|
|
||||||
connect_to_node,
|
|
||||||
fetch_claims,
|
|
||||||
} from '@coinweb/wallet-lib';
|
|
||||||
import { type Pagination } from 'dex-app.cm';
|
|
||||||
|
|
||||||
import { Currency, CONTRACT_PARAMS } from '../constants';
|
|
||||||
|
|
||||||
type IndexClaimKey = {
|
|
||||||
first_part: unknown[];
|
|
||||||
second_part: [number | string, ...unknown[]];
|
|
||||||
};
|
|
||||||
|
|
||||||
let cwebWalletNode: NodeConnection | null = null;
|
|
||||||
const getCwebWalletNode = () => {
|
|
||||||
if (!cwebWalletNode) {
|
|
||||||
cwebWalletNode = connect_to_node(import.meta.env.VITE_API_URL as string);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cwebWalletNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ContractClient = {
|
|
||||||
fetchClaims: <T>(
|
|
||||||
firstPart: NonNullable<unknown>,
|
|
||||||
secondPart: T,
|
|
||||||
range?: {
|
|
||||||
start: T extends null ? unknown : T | null;
|
|
||||||
end: T extends null ? unknown : T | null;
|
|
||||||
},
|
|
||||||
pagination?: Pagination
|
|
||||||
) => Promise<GqlIssuedClaim[]>;
|
|
||||||
updateClient: () => void;
|
|
||||||
contractAddress: string;
|
|
||||||
networkName: NetworkName;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createClient = (contractAddress: string, networkName: NetworkName) => {
|
|
||||||
const createClaimFilter = <T extends NonNullable<unknown>>(
|
|
||||||
firstPart: NonNullable<unknown>,
|
|
||||||
secondPart: T | null = null,
|
|
||||||
range?: {
|
|
||||||
start: T;
|
|
||||||
end: T;
|
|
||||||
}
|
|
||||||
): GqlClaimFilter => {
|
|
||||||
return {
|
|
||||||
issuer: { FromSmartContract: contractAddress },
|
|
||||||
keyFirstPart: firstPart,
|
|
||||||
keySecondPart: secondPart,
|
|
||||||
startsAtKeySecondPart: range?.start ?? null,
|
|
||||||
endsAtKeySecondPart: range?.end ?? null,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchClaims = async <T extends NonNullable<unknown>>(
|
|
||||||
firstPart: NonNullable<unknown>,
|
|
||||||
secondPart: T | null = null,
|
|
||||||
range?: {
|
|
||||||
start: T;
|
|
||||||
end: T;
|
|
||||||
},
|
|
||||||
pagination?: Pagination
|
|
||||||
): Promise<GqlIssuedClaim[]> => {
|
|
||||||
if (!contractAddress || !networkName) {
|
|
||||||
throw new Error('Network parameters are not defined');
|
|
||||||
}
|
|
||||||
|
|
||||||
const filter = createClaimFilter(firstPart, secondPart, range);
|
|
||||||
|
|
||||||
const loadClaims = async () => {
|
|
||||||
const rawData = await fetch_claims(getCwebWalletNode(), [filter], networkName, true);
|
|
||||||
|
|
||||||
const sortedData = rawData.sort((a, b) => {
|
|
||||||
let aValue: string | number | bigint = (a.content.key as IndexClaimKey).second_part[0];
|
|
||||||
let bValue: string | number | bigint = (b.content.key as IndexClaimKey).second_part[0];
|
|
||||||
|
|
||||||
if (typeof aValue === 'string' && typeof bValue === 'string') {
|
|
||||||
if (aValue.startsWith('0x')) {
|
|
||||||
aValue = BigInt(aValue);
|
|
||||||
} else {
|
|
||||||
aValue = BigInt(`0x${aValue}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bValue.startsWith('0x')) {
|
|
||||||
bValue = BigInt(bValue);
|
|
||||||
} else {
|
|
||||||
bValue = BigInt(`0x${bValue}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aValue < bValue) {
|
|
||||||
return 1;
|
|
||||||
} else if ((a.content.key as IndexClaimKey).second_part[0] > (b.content.key as IndexClaimKey).second_part[0]) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (pagination) {
|
|
||||||
if (pagination.offset < 0) {
|
|
||||||
return sortedData.slice(0, pagination.limit + pagination.offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortedData.slice(pagination.offset, pagination.offset + pagination.limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortedData;
|
|
||||||
};
|
|
||||||
|
|
||||||
const claimsRequest = loadClaims();
|
|
||||||
|
|
||||||
const result = await claimsRequest;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateClient = () => {};
|
|
||||||
|
|
||||||
return {
|
|
||||||
fetchClaims,
|
|
||||||
updateClient,
|
|
||||||
contractAddress,
|
|
||||||
networkName,
|
|
||||||
} as ContractClient;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const baseClients: {
|
|
||||||
[key in Exclude<Currency, Currency.CWEB>]: ContractClient;
|
|
||||||
} = {
|
|
||||||
[Currency.ETH]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.ETH].L2_CONTRACT_ADDRESS_BASE,
|
|
||||||
CONTRACT_PARAMS[Currency.ETH].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.BNB]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.BNB].L2_CONTRACT_ADDRESS_BASE,
|
|
||||||
CONTRACT_PARAMS[Currency.BNB].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.USDT_ETH]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.USDT_ETH].L2_CONTRACT_ADDRESS_BASE,
|
|
||||||
CONTRACT_PARAMS[Currency.USDT_ETH].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.USDT_BNB]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.USDT_BNB].L2_CONTRACT_ADDRESS_BASE,
|
|
||||||
CONTRACT_PARAMS[Currency.USDT_BNB].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.BTC]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.BTC].L2_CONTRACT_ADDRESS_BASE,
|
|
||||||
CONTRACT_PARAMS[Currency.BTC].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.LTC]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.BTC].L2_CONTRACT_ADDRESS_BASE,
|
|
||||||
CONTRACT_PARAMS[Currency.BTC].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.EGLD]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.BTC].L2_CONTRACT_ADDRESS_BASE,
|
|
||||||
CONTRACT_PARAMS[Currency.BTC].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.TRX]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.TRX].L2_CONTRACT_ADDRESS_BASE,
|
|
||||||
CONTRACT_PARAMS[Currency.TRX].NETWORK_NAME
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const makerClients: {
|
|
||||||
[key in Exclude<Currency, Currency.CWEB>]: ContractClient;
|
|
||||||
} = {
|
|
||||||
[Currency.ETH]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.ETH].L2_CONTRACT_ADDRESS_MAKER,
|
|
||||||
CONTRACT_PARAMS[Currency.ETH].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.BNB]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.BNB].L2_CONTRACT_ADDRESS_MAKER,
|
|
||||||
CONTRACT_PARAMS[Currency.BNB].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.USDT_ETH]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.USDT_ETH].L2_CONTRACT_ADDRESS_MAKER,
|
|
||||||
CONTRACT_PARAMS[Currency.USDT_ETH].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.USDT_BNB]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.USDT_BNB].L2_CONTRACT_ADDRESS_MAKER,
|
|
||||||
CONTRACT_PARAMS[Currency.USDT_BNB].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.BTC]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.BTC].L2_CONTRACT_ADDRESS_MAKER,
|
|
||||||
CONTRACT_PARAMS[Currency.BTC].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.LTC]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.BTC].L2_CONTRACT_ADDRESS_MAKER,
|
|
||||||
CONTRACT_PARAMS[Currency.BTC].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.EGLD]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.BTC].L2_CONTRACT_ADDRESS_MAKER,
|
|
||||||
CONTRACT_PARAMS[Currency.BTC].NETWORK_NAME
|
|
||||||
),
|
|
||||||
[Currency.TRX]: createClient(
|
|
||||||
CONTRACT_PARAMS[Currency.TRX].L2_CONTRACT_ADDRESS_MAKER,
|
|
||||||
CONTRACT_PARAMS[Currency.TRX].NETWORK_NAME
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const clients: Record<string, ContractClient> = new Proxy(
|
|
||||||
Object.fromEntries(
|
|
||||||
[...Object.values(baseClients), ...Object.values(makerClients)].map((client) => [client.contractAddress, client])
|
|
||||||
),
|
|
||||||
{
|
|
||||||
get(clients, contractId: string) {
|
|
||||||
if (!(contractId in clients)) {
|
|
||||||
throw new Error(`Client for contractId=${String(contractId)} not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return clients[contractId];
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import { type Client, getAllActiveOrders, type Pagination } from 'market-maker.cm';
|
|
||||||
|
|
||||||
import { Currency } from '../constants';
|
|
||||||
|
|
||||||
import { makerClients } from './client';
|
|
||||||
|
|
||||||
export const getActiveMarketOrders = async (currency: Currency, pagination?: Pagination) => {
|
|
||||||
if (currency === Currency.CWEB) {
|
|
||||||
throw new Error(`Cannot get active orders for: ${currency}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client: Client = {
|
|
||||||
fetchClaims: (...params) => makerClients[currency].fetchClaims(...params, pagination),
|
|
||||||
};
|
|
||||||
|
|
||||||
return getAllActiveOrders(client);
|
|
||||||
};
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import { type Client, type Pagination, getOrderById, getActiveOrderIds, type OrderData } from 'dex-app.cm';
|
|
||||||
|
|
||||||
// import { decimalsForCurrency } from '@/utils';
|
|
||||||
|
|
||||||
import { Currency } from '../constants';
|
|
||||||
|
|
||||||
import { baseClients } from './client';
|
|
||||||
|
|
||||||
export const getAllActivePositions = async (currency: Currency, pagination?: Pagination): Promise<OrderData[]> => {
|
|
||||||
if (currency === Currency.CWEB) {
|
|
||||||
throw new Error(`Cannot get active positions for: ${currency}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client: Client = {
|
|
||||||
fetchClaims: (...params) => baseClients[currency].fetchClaims(...params, pagination),
|
|
||||||
};
|
|
||||||
|
|
||||||
const ids = await getActiveOrderIds(client);
|
|
||||||
return await Promise.all(ids.map((id: string) => getOrderById(client, id)));
|
|
||||||
/*
|
|
||||||
return (
|
|
||||||
(await Promise.all(ids.map((id: string) => getOrderById(client, id))))
|
|
||||||
// TODO: Remove this filter when we have a proper solution for reversed orders
|
|
||||||
.filter((position) => {
|
|
||||||
if (['btcswap'].includes(import.meta.env.MODE)) {
|
|
||||||
return position.l1Amount === position.minL1Amount; // order.isOwnerless
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
position.funds >= 1n * 10n ** BigInt(decimalsForCurrency(Currency.CWEB)) &&
|
|
||||||
position.l1Amount !== position.minL1Amount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { type PubKey } from '@coinweb/wallet-lib';
|
|
||||||
import { type Client, getAllOwnClaims, type Pagination } from 'market-maker.cm';
|
|
||||||
|
|
||||||
import { Currency } from '../constants';
|
|
||||||
|
|
||||||
import { makerClients } from './client';
|
|
||||||
|
|
||||||
export const getAllMarketClaim = async (currency: Currency, pubKey: PubKey, pagination?: Pagination) => {
|
|
||||||
if (currency === Currency.CWEB) {
|
|
||||||
throw new Error(`Cannot get all market claim for: ${currency}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client: Client = {
|
|
||||||
fetchClaims: (...params) => makerClients[currency].fetchClaims(...params, pagination),
|
|
||||||
};
|
|
||||||
|
|
||||||
return getAllOwnClaims(client, {
|
|
||||||
auth: 'EcdsaContract',
|
|
||||||
payload: pubKey,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { type PubKey } from '@coinweb/wallet-lib';
|
|
||||||
import { type Client, getBalance, type Pagination } from 'market-maker.cm';
|
|
||||||
|
|
||||||
import { Currency } from '../constants';
|
|
||||||
|
|
||||||
import { makerClients } from './client';
|
|
||||||
|
|
||||||
export const getAllMarketCollateralBalance = async (currency: Currency, pubKey: PubKey, pagination?: Pagination) => {
|
|
||||||
if (currency === Currency.CWEB) {
|
|
||||||
throw new Error(`Cannot get market balance for: ${currency}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client: Client = {
|
|
||||||
fetchClaims: (...params) => makerClients[currency].fetchClaims(...params, pagination),
|
|
||||||
};
|
|
||||||
|
|
||||||
return getBalance(client, {
|
|
||||||
auth: 'EcdsaContract',
|
|
||||||
payload: pubKey,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import { type Client, type Pagination, type OrderData, getUserOrderIds, getOrderById, type PubKey } from 'dex-app.cm';
|
|
||||||
|
|
||||||
import { Currency } from '../constants';
|
|
||||||
|
|
||||||
import { baseClients } from './client';
|
|
||||||
|
|
||||||
export const getAllUserPositions = async (
|
|
||||||
currency: Currency,
|
|
||||||
pubKey: PubKey,
|
|
||||||
pagination?: Pagination
|
|
||||||
): Promise<OrderData[]> => {
|
|
||||||
if (currency === Currency.CWEB) {
|
|
||||||
throw new Error(`Cannot get user positions for: ${currency}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client: Client = {
|
|
||||||
fetchClaims: (...params) => {
|
|
||||||
if (params.length < 3) {
|
|
||||||
params.push(pagination);
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseClients[currency].fetchClaims(...params, pagination);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const noPaginationClient: Client = {
|
|
||||||
fetchClaims: (...params) => baseClients[currency].fetchClaims(...params),
|
|
||||||
};
|
|
||||||
|
|
||||||
const ids = await getUserOrderIds(client, {
|
|
||||||
auth: 'EcdsaContract',
|
|
||||||
payload: pubKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.all(ids.map((id: string) => getOrderById(noPaginationClient, id)));
|
|
||||||
};
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import { type Client, type Pagination, getUserOrderIds, type PubKey } from 'dex-app.cm';
|
|
||||||
|
|
||||||
import { Currency } from '../constants';
|
|
||||||
|
|
||||||
import { baseClients } from './client';
|
|
||||||
|
|
||||||
export const getAllUserPositionsCount = async (
|
|
||||||
currency: Currency,
|
|
||||||
pubKey: PubKey,
|
|
||||||
pagination?: Pagination
|
|
||||||
): Promise<number> => {
|
|
||||||
if (currency === Currency.CWEB) {
|
|
||||||
throw new Error(`Cannot get user positions for: ${currency}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client: Client = {
|
|
||||||
fetchClaims: (...params) => {
|
|
||||||
if (params.length < 3) {
|
|
||||||
params.push(pagination);
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseClients[currency].fetchClaims(...params, pagination);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const ids = await getUserOrderIds(client, {
|
|
||||||
auth: 'EcdsaContract',
|
|
||||||
payload: pubKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
return ids.length;
|
|
||||||
};
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import { type Client, getBestActiveOrders, type Pagination } from 'market-maker.cm';
|
|
||||||
|
|
||||||
import { Currency } from '../constants';
|
|
||||||
|
|
||||||
import { makerClients } from './client';
|
|
||||||
|
|
||||||
export const getBestActiveMarketOrders = async (currency: Currency, pagination?: Pagination) => {
|
|
||||||
if (currency === Currency.CWEB) {
|
|
||||||
throw new Error(`Cannot get best active market orders for: ${currency}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client: Client = {
|
|
||||||
fetchClaims: (...params) => makerClients[currency].fetchClaims(...params, pagination),
|
|
||||||
};
|
|
||||||
|
|
||||||
return getBestActiveOrders(client);
|
|
||||||
};
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
import { type Client, type Pagination, getOrderById, getBestActiveOrderIds } from 'dex-app.cm';
|
|
||||||
|
|
||||||
import { getTxOutputDataByVout } from '@/networks/btc';
|
|
||||||
import { C1Position } from '@/types';
|
|
||||||
import { isBtcCurrency, decimalsForCurrency } from '@/utils';
|
|
||||||
|
|
||||||
import { Currency } from '../constants';
|
|
||||||
|
|
||||||
import { baseClients } from './client';
|
|
||||||
|
|
||||||
export const getBestActivePositions = async <TCurrency extends Currency>(
|
|
||||||
currency: TCurrency,
|
|
||||||
pagination?: Pagination
|
|
||||||
): Promise<C1Position<TCurrency>[]> => {
|
|
||||||
if (currency === Currency.CWEB) {
|
|
||||||
throw new Error(`Cannot get best active positions for: ${currency}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client: Client = {
|
|
||||||
fetchClaims: (...params) => baseClients[currency as Exclude<TCurrency, 'CWEB'>].fetchClaims(...params, pagination),
|
|
||||||
};
|
|
||||||
|
|
||||||
const ids = await getBestActiveOrderIds(client);
|
|
||||||
|
|
||||||
const positions = await Promise.all(
|
|
||||||
ids.map((id: string) => {
|
|
||||||
return getOrderById(client, id).then(async (position) => {
|
|
||||||
// Check if the BTC dust lock UTXO is spent, and filter out the position if it is
|
|
||||||
if (isBtcCurrency(currency)) {
|
|
||||||
const { l1TxId, vout } = (position.chainData || {}) as { l1TxId: string; vout: number };
|
|
||||||
if (l1TxId && Number.isInteger(vout)) {
|
|
||||||
const utxo = await getTxOutputDataByVout(l1TxId, Number(vout));
|
|
||||||
if (utxo.spent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return position;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then((positions) => positions.filter((position): position is NonNullable<typeof position> => Boolean(position)))
|
|
||||||
.then((positions) => {
|
|
||||||
return (
|
|
||||||
positions
|
|
||||||
// TODO: Remove this filter when we have a proper solution for reversed orders
|
|
||||||
.filter((position) => {
|
|
||||||
if (['btcswap'].includes(import.meta.env.MODE)) {
|
|
||||||
return position.l1Amount === position.minL1Amount;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
position.funds >= 1n * 10n ** BigInt(decimalsForCurrency(Currency.CWEB)) &&
|
|
||||||
position.l1Amount !== position.minL1Amount
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return positions as unknown as C1Position<TCurrency>[];
|
|
||||||
};
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import { Client, getHistoryAccess as getHistoryAccessFromContract, HistoryAccessClaimBody } from 'dex-app.cm';
|
|
||||||
|
|
||||||
import { getContractIdsFromRoute } from '@/components/trackers/HistoryDiagram/utils';
|
|
||||||
import { HistoryRoute } from '@/types';
|
|
||||||
|
|
||||||
import { clients } from './client';
|
|
||||||
|
|
||||||
export const getHistoryAccess = async (unique: string, route?: HistoryRoute) => {
|
|
||||||
const contractIds = getContractIdsFromRoute({
|
|
||||||
route,
|
|
||||||
});
|
|
||||||
|
|
||||||
const results = await Promise.allSettled(
|
|
||||||
contractIds.map((contractId) => {
|
|
||||||
const client: Client = {
|
|
||||||
fetchClaims: (...params) => clients[contractId].fetchClaims(...params),
|
|
||||||
};
|
|
||||||
|
|
||||||
return getHistoryAccessFromContract(client, unique);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const access = results.find(
|
|
||||||
(result): result is PromiseFulfilledResult<HistoryAccessClaimBody> =>
|
|
||||||
result.status === 'fulfilled' && !!result.value
|
|
||||||
)?.value;
|
|
||||||
|
|
||||||
return access ?? null;
|
|
||||||
};
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user