punkkit-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +47 -0
- package/.gitmodules +3 -0
- package/README.md +158 -0
- package/config/auction.config.ts +40 -0
- package/config/env.config.ts +204 -0
- package/config/uniswap.config.ts +107 -0
- package/config/voucher.config.ts +10 -0
- package/contracts/MutiVoucher.sol +78 -0
- package/contracts/auction/ChainXAuction.sol +177 -0
- package/contracts/auction/ChainXAuctionV2.sol +672 -0
- package/contracts/auction/ChainXWrappedETH.sol +80 -0
- package/contracts/auction/ChainYLiquidityManager.sol +57 -0
- package/contracts/auction/ChainYShadowETH.sol +148 -0
- package/contracts/auction/ChainYVault.sol +195 -0
- package/contracts/auction/ChainYVaultCoinbase.sol +276 -0
- package/contracts/auction/ChainYVaultV2.sol +318 -0
- package/contracts/auction/coinbase-and-stake/README.md +55 -0
- package/contracts/auction/coinbase-and-stake/coinbase.sol +142 -0
- package/contracts/auction/coinbase-and-stake/invokeCoinbase.sol +159 -0
- package/contracts/auction/coinbase-and-stake/invokeStake.sol +82 -0
- package/contracts/auction/coinbase-and-stake/stake.sol +92 -0
- package/contracts/auction/interfaces/IUniswapV2Factory.sol +15 -0
- package/contracts/auction/interfaces/IUniswapV2Pair.sol +53 -0
- package/contracts/auction/interfaces/IUniswapV2Router02.sol +25 -0
- package/contracts/auction/interfaces/IUnlockStrategy.sol +18 -0
- package/contracts/auction/libraries/EventParser.sol +32 -0
- package/contracts/auction/libraries/TransactionParser.sol +70 -0
- package/contracts/auction/strategies/MatchResultWithdrawnStrategy.sol +33 -0
- package/contracts/auction/utils/BytesLib.sol +180 -0
- package/contracts/auction/utils/RLPReader.sol +355 -0
- package/contracts/uniswap/Create2.sol +80 -0
- package/contracts/uniswap/DynamicFee.sol +100 -0
- package/contracts/uniswap/Example.sol +35 -0
- package/contracts/uniswap/HookMiner.sol +52 -0
- package/contracts/uniswap/LimitOrder.sol +486 -0
- package/contracts/uniswap/LiquidPool.sol +179 -0
- package/contracts/uniswap/MockERC20.sol +20 -0
- package/hardhat.config.ts +35 -0
- package/ignition/modules/LimitOrder.ts +33 -0
- package/package.json +32 -0
- package/scripts/auction/deploy.ts +23 -0
- package/scripts/auction/deployCoinbase.ts +21 -0
- package/scripts/auction/deployXAuction.ts +23 -0
- package/scripts/auction/deployYVault.ts +22 -0
- package/scripts/deploy_voucher.ts +20 -0
- package/scripts/uniswap/deploy/deploy.ts +65 -0
- package/scripts/uniswap/deploy/deploy_create2.ts +11 -0
- package/scripts/uniswap/deploy/deploy_example.ts +35 -0
- package/scripts/uniswap/deploy/deploy_hooks.ts +74 -0
- package/scripts/uniswap/deploy/deploy_mockERC20.ts +42 -0
- package/scripts/uniswap/deploy/help.ts +96 -0
- package/scripts/uniswap/deploy/init.ts +70 -0
- package/src/auction/chainXAuction.ts +209 -0
- package/src/auction/chainYVault.ts +153 -0
- package/src/auction/event.ts +19 -0
- package/src/auction/serialize.ts +162 -0
- package/src/auction/type.ts +71 -0
- package/src/lib/signer.ts +20 -0
- package/src/lib/unlock.ts +14 -0
- package/src/uniswap/1-marketprice/addLiquidity.ts +80 -0
- package/src/uniswap/1-marketprice/removeLiquidity.ts +63 -0
- package/src/uniswap/1-marketprice/swap.ts +100 -0
- package/src/uniswap/2-limitorder/kill.ts +70 -0
- package/src/uniswap/2-limitorder/place.ts +93 -0
- package/src/uniswap/2-limitorder/withdraw.ts +78 -0
- package/src/uniswap/3-dynamicfee/dynamicfee.ts +321 -0
- package/src/uniswap/lib/ERC20.ts +49 -0
- package/src/uniswap/lib/contract.ts +18 -0
- package/src/uniswap/lib/limitOrder.ts +40 -0
- package/src/uniswap/lib/liqCalculation.ts +152 -0
- package/src/uniswap/lib/listen.ts +57 -0
- package/src/uniswap/lib/pool.ts +62 -0
- package/src/uniswap/lib/swap.ts +8 -0
- package/src/uniswap/lib/types.ts +21 -0
- package/src/uniswap/lib/utils.ts +26 -0
- package/src/uniswap/playgroud/abiencode.ts +21 -0
- package/src/uniswap/playgroud/amount0.ts +47 -0
- package/src/uniswap/playgroud/errordecode.ts +54 -0
- package/src/uniswap/playgroud/errorsigs.ts +86 -0
- package/src/voucher.ts +122 -0
- package/test/auction/ChainXAuctionV2.test.ts +265 -0
- package/test/auction/ChainYVaultV2.test.js +163 -0
- package/test/auction/ChainYVaultV2.test.ts +183 -0
- package/test/auction/auction.test.ts +106 -0
- package/test/connect_punk.test.ts +26 -0
- package/test/create2.test.ts +44 -0
- package/test/normal.ts +43 -0
- package/test/test-config.ts +18 -0
- package/test/uniswap/example.test.ts +62 -0
- package/test/uniswap/limitOrder.test.ts +184 -0
- package/test/uniswap/mockERC20.test.ts +142 -0
- package/test/voucher_hardhat.test.ts +120 -0
- package/test/voucher_punk.test.ts +83 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ethers } from "hardhat";
|
|
2
|
+
import type { Contract } from "ethers";
|
|
3
|
+
import { CONTRACTS } from "../../../config/uniswap.config";
|
|
4
|
+
import { bigintToBytes32, bytecodeWithArgs } from "../../../src/uniswap/lib/utils";
|
|
5
|
+
|
|
6
|
+
// Constants that correspond to the ones in Solidity
|
|
7
|
+
const FLAG_MASK = BigInt(0x3FFF);
|
|
8
|
+
const MAX_LOOP = 100_000;
|
|
9
|
+
|
|
10
|
+
// create2Deploy deploys a contract using CREATE2 via the Create2 factory contract
|
|
11
|
+
export async function create2Deploy(create2: Contract, contractName: string, types: string[], params: any[], salt: bigint): Promise<string> {
|
|
12
|
+
|
|
13
|
+
// Load the contract artifact using TypeScript
|
|
14
|
+
const scArtifact = CONTRACTS[contractName];
|
|
15
|
+
if (scArtifact == null) {
|
|
16
|
+
throw new Error(`Contract artifact for ${contractName} not found`);
|
|
17
|
+
}
|
|
18
|
+
let bytecode = scArtifact.bytecode;
|
|
19
|
+
|
|
20
|
+
if (bytecode == undefined || bytecode.length == 0) {
|
|
21
|
+
console.log("bytecode length is zero");
|
|
22
|
+
throw new Error(`Bytecode for contract ${contractName} is undefined or empty`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const initCode = bytecodeWithArgs(bytecode, types, params);
|
|
26
|
+
const create2Addr = getCreate2Address(await create2.address, salt, initCode);
|
|
27
|
+
// console.log(`precomputed ${contractName} address: ${create2Addr}`);
|
|
28
|
+
|
|
29
|
+
const saltHex = bigintToBytes32(salt);
|
|
30
|
+
|
|
31
|
+
console.log("create2 address",create2.address);
|
|
32
|
+
|
|
33
|
+
const tx = await create2.deployCreate2WithSalt(initCode, saltHex, { gasLimit: 30_000_000 });
|
|
34
|
+
await tx.wait();
|
|
35
|
+
|
|
36
|
+
console.log(`deterministic deployed ${contractName} by transaction(${tx.hash})`);
|
|
37
|
+
return create2Addr;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Simulate the HookMiner.find function in TypeScript
|
|
41
|
+
export async function findHookAddress(
|
|
42
|
+
deployer: string,
|
|
43
|
+
contractName: string,
|
|
44
|
+
types: string[],
|
|
45
|
+
params: any[],
|
|
46
|
+
flags: bigint
|
|
47
|
+
): Promise<{ hookAddress: string, salt: bigint }> {
|
|
48
|
+
// Load the contract artifact using TypeScript
|
|
49
|
+
const bytecode = CONTRACTS[contractName].bytecode;
|
|
50
|
+
if (bytecode == undefined || bytecode.length == 0) {
|
|
51
|
+
throw new Error(`Bytecode for contract ${contractName} is undefined or empty`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const creationCodeWithArgs = bytecodeWithArgs(bytecode, types, params);
|
|
55
|
+
|
|
56
|
+
let salt = BigInt(0);
|
|
57
|
+
for (let i = 0; i < MAX_LOOP; i++) {
|
|
58
|
+
const hookAddress = getCreate2Address(deployer, salt, creationCodeWithArgs);
|
|
59
|
+
if (hookAddress && (BigInt(hookAddress) & FLAG_MASK) === flags) {
|
|
60
|
+
// Check if the address is not deployed
|
|
61
|
+
const code = await ethers.provider.getCode(hookAddress);
|
|
62
|
+
if (code === "0x") {
|
|
63
|
+
return { hookAddress, salt };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
salt = salt + BigInt(1);
|
|
67
|
+
}
|
|
68
|
+
throw new Error("HookMiner: could not find salt");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getCreate2Address(factoryAddress: string, salt: bigint, initCode: string): string {
|
|
72
|
+
const saltHex = bigintToBytes32(salt);
|
|
73
|
+
const create2Addr = ethers.utils.getCreate2Address(factoryAddress, saltHex, ethers.utils.keccak256(initCode));
|
|
74
|
+
return create2Addr;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
export async function deployHookWithFlags(factory: Contract, factoryAddr: string, contractName: string, constructorTypes: string[], constructorValues: any[], flags: bigint) {
|
|
79
|
+
// 1. compute hook address
|
|
80
|
+
const { hookAddress, salt } = await findHookAddress(
|
|
81
|
+
factoryAddr,
|
|
82
|
+
contractName,
|
|
83
|
+
constructorTypes,
|
|
84
|
+
constructorValues,
|
|
85
|
+
flags
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
console.log(`\n=== Deploying ${contractName} Hook ===`);
|
|
89
|
+
console.log("Hook Address:", hookAddress);
|
|
90
|
+
console.log("Salt Used:", salt.toString());
|
|
91
|
+
console.log("Salt Keccak256:", ethers.utils.id(salt.toString()));
|
|
92
|
+
|
|
93
|
+
// 2. use CREATE2 deploy
|
|
94
|
+
await create2Deploy(factory,contractName,constructorTypes,constructorValues,salt);
|
|
95
|
+
return hookAddress;
|
|
96
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ethers } from "hardhat";
|
|
2
|
+
import { CONTRACT_ADDRESSES, CONTRACTS, POOL_KEYS, RPC_URL, PRIVATE_KEY, PRICE_INIT, INITIAL_SUPPLY } from "../../../config/uniswap.config";
|
|
3
|
+
import { priceToSqrtPrice } from "../../../src/uniswap/lib/liqCalculation";
|
|
4
|
+
import { getERC20Balance, mintERC20 } from "../../../src/uniswap/lib/ERC20";
|
|
5
|
+
import { getContract } from "../../../src/uniswap/lib/contract";
|
|
6
|
+
import { Wallet,Contract } from "ethers";
|
|
7
|
+
import { get } from "http";
|
|
8
|
+
|
|
9
|
+
async function isPoolInitialized( poolManager: Contract, poolKey: any,sqrtPriceX96:bigint): Promise<boolean> {
|
|
10
|
+
try {
|
|
11
|
+
// callStatic 用于模拟调用,不会产生交易。如果报错说明已经初始化
|
|
12
|
+
await poolManager.callStatic.initialize(poolKey, sqrtPriceX96);
|
|
13
|
+
return false;
|
|
14
|
+
} catch {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
// InitPoolManager must be called only token is deployed.
|
|
21
|
+
export async function initPoolManager(contractAddress: string) {
|
|
22
|
+
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
|
|
23
|
+
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
|
|
24
|
+
const constract = new ethers.Contract(contractAddress, CONTRACTS["PoolManager"].abi, wallet);
|
|
25
|
+
const price = PRICE_INIT
|
|
26
|
+
const sqrtPriceX96 = priceToSqrtPrice(price);
|
|
27
|
+
// console.log(`sqrtPriceX96: ${sqrtPriceX96}`);
|
|
28
|
+
if (await isPoolInitialized(constract, POOL_KEYS.limitOrderPoolKey, sqrtPriceX96)) {
|
|
29
|
+
console.log("Limit Order Pool is already initialized.");
|
|
30
|
+
}else{
|
|
31
|
+
await constract.initialize(POOL_KEYS.limitOrderPoolKey, sqrtPriceX96);
|
|
32
|
+
}
|
|
33
|
+
if (await isPoolInitialized(constract, POOL_KEYS.dynamicFeePoolKey, sqrtPriceX96)) {
|
|
34
|
+
console.log("Dynamic Fee Pool is already initialized.");
|
|
35
|
+
}else{
|
|
36
|
+
await constract.initialize(POOL_KEYS.dynamicFeePoolKey, sqrtPriceX96);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function ERC20Initial(wallet: Wallet, contractName: string, account:string, supply: bigint) {
|
|
41
|
+
const token = await getContract(wallet, contractName);
|
|
42
|
+
// 判断是否已经初始化
|
|
43
|
+
if (await getERC20Balance(token,account) !== BigInt(0)) {
|
|
44
|
+
console.log(`${contractName} already initialized for account ${account}`);
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
await mintERC20(token, account, supply);
|
|
48
|
+
// 检查是否初始化成功
|
|
49
|
+
const balance = await getERC20Balance(token,account)
|
|
50
|
+
if (balance !== supply) {
|
|
51
|
+
throw new Error(`${contractName} mint failed. Acount ${account} balance is ${balance}, expected ${supply}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function main() {
|
|
56
|
+
// 1. PoolManager initial
|
|
57
|
+
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
|
|
58
|
+
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
|
|
59
|
+
const walletAddress = await wallet.getAddress();
|
|
60
|
+
await initPoolManager(CONTRACT_ADDRESSES["PoolManager"]);
|
|
61
|
+
|
|
62
|
+
// 2. ERC20 initial
|
|
63
|
+
const supply = INITIAL_SUPPLY;
|
|
64
|
+
await ERC20Initial(wallet,"Token0", walletAddress, supply);
|
|
65
|
+
await ERC20Initial(wallet,"Token1", walletAddress, supply);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (require.main === module) {
|
|
69
|
+
main();
|
|
70
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
|
|
2
|
+
import { RPC_URL, PRIVATE_KEY, AUCTION_ADDR, AUCTION_ABI } from "../../config/auction.config";
|
|
3
|
+
import { getAuctionID, buildAuctionCreatedReceipt, buildAuctionCreatedTx } from "./serialize"; // 路径按需调整
|
|
4
|
+
import { Contract, Signer, utils } from "ethers";
|
|
5
|
+
import { getSigner } from "../lib/signer"
|
|
6
|
+
import {
|
|
7
|
+
CreateAuctionParams,
|
|
8
|
+
PlaceBidParams,
|
|
9
|
+
RevealBidParams,
|
|
10
|
+
RevealLockParams,
|
|
11
|
+
SubmitMatchResultsParams,
|
|
12
|
+
WithdrawMatchResultParams,
|
|
13
|
+
} from "./type";
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export function getAuctionContract(
|
|
17
|
+
signer: Signer
|
|
18
|
+
): Contract {
|
|
19
|
+
if (!AUCTION_ADDR.chainXAuction) {
|
|
20
|
+
throw new Error("chainXAuctionV2Addr not set");
|
|
21
|
+
}
|
|
22
|
+
return new Contract(
|
|
23
|
+
AUCTION_ADDR.chainXAuction,
|
|
24
|
+
AUCTION_ABI.chainXAuction.abi,
|
|
25
|
+
signer
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function createAuction(
|
|
30
|
+
params: CreateAuctionParams
|
|
31
|
+
): Promise<{ auctionId: string; revealStartTime: bigint }> {
|
|
32
|
+
const { seller, sourceChainId, activeAuctionCount, auctionType } = params;
|
|
33
|
+
|
|
34
|
+
const signer = getSigner(RPC_URL, PRIVATE_KEY);
|
|
35
|
+
const provider = signer.provider!;
|
|
36
|
+
const nonce= await provider.getTransactionCount(signer.address);
|
|
37
|
+
|
|
38
|
+
const chainId =
|
|
39
|
+
sourceChainId ?? (await provider.getNetwork()).chainId;
|
|
40
|
+
|
|
41
|
+
const auctionId = getAuctionID(
|
|
42
|
+
seller,
|
|
43
|
+
chainId,
|
|
44
|
+
AUCTION_ADDR.chainYVault,
|
|
45
|
+
activeAuctionCount
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const rawTx = await buildAuctionCreatedTx({
|
|
49
|
+
nonce: nonce,
|
|
50
|
+
gasPrice: utils.parseUnits("1", "gwei").toBigInt(),
|
|
51
|
+
gasLimit: BigInt(52_200),
|
|
52
|
+
to: AUCTION_ADDR.chainYVault,
|
|
53
|
+
value: BigInt(0),
|
|
54
|
+
chainId:chainId,
|
|
55
|
+
data: "0x",
|
|
56
|
+
});
|
|
57
|
+
const signedTx = await signer.signTransaction(rawTx);
|
|
58
|
+
const revealStartTime = BigInt(Math.floor(Date.now() / 1000) + 60);
|
|
59
|
+
|
|
60
|
+
const rawRecpt = buildAuctionCreatedReceipt({
|
|
61
|
+
auctionId,
|
|
62
|
+
auctionType,
|
|
63
|
+
activeAuctionCount: activeAuctionCount,
|
|
64
|
+
revealTime: revealStartTime,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const crossChainMessage =
|
|
68
|
+
utils.defaultAbiCoder.encode(
|
|
69
|
+
["tuple(uint256, bytes, bytes)"],
|
|
70
|
+
[[chainId, signedTx, rawRecpt]]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const contract = getAuctionContract(signer);
|
|
74
|
+
const tx = await contract.createAuction(crossChainMessage);
|
|
75
|
+
await tx.wait();
|
|
76
|
+
|
|
77
|
+
return { auctionId, revealStartTime };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
export async function placeBid(
|
|
82
|
+
params: PlaceBidParams
|
|
83
|
+
): Promise<{ bidValue: string; secretHash: string }> {
|
|
84
|
+
const { auctionId, seller, valueEth, secretText = "secret" } = params;
|
|
85
|
+
|
|
86
|
+
const signer = getSigner(RPC_URL, PRIVATE_KEY);
|
|
87
|
+
const contract = getAuctionContract(signer);
|
|
88
|
+
|
|
89
|
+
const value = utils.parseEther(valueEth);
|
|
90
|
+
const bidValue = utils.hexZeroPad(
|
|
91
|
+
value.toHexString(),
|
|
92
|
+
32
|
|
93
|
+
);
|
|
94
|
+
const secretHash = utils.keccak256(
|
|
95
|
+
utils.toUtf8Bytes(secretText)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const depositAmount = value.mul(15).div(10);
|
|
99
|
+
|
|
100
|
+
const tx = await contract.placeBid(
|
|
101
|
+
auctionId,
|
|
102
|
+
seller,
|
|
103
|
+
secretHash,
|
|
104
|
+
bidValue,
|
|
105
|
+
{ value: depositAmount }
|
|
106
|
+
);
|
|
107
|
+
await tx.wait();
|
|
108
|
+
|
|
109
|
+
return { bidValue, secretHash };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function revealBid(
|
|
113
|
+
params: RevealBidParams
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
const signer = getSigner(RPC_URL, PRIVATE_KEY);
|
|
116
|
+
const contract = getAuctionContract(signer);
|
|
117
|
+
|
|
118
|
+
const value = utils.parseEther(params.valueEth);
|
|
119
|
+
const salt =
|
|
120
|
+
params.saltHex ?? utils.randomBytes(32);
|
|
121
|
+
|
|
122
|
+
const tx = await contract.revealBid(
|
|
123
|
+
params.auctionId,
|
|
124
|
+
value,
|
|
125
|
+
salt
|
|
126
|
+
);
|
|
127
|
+
await tx.wait();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function revealLock(
|
|
131
|
+
params: RevealLockParams
|
|
132
|
+
): Promise<void> {
|
|
133
|
+
const signer = getSigner(RPC_URL, PRIVATE_KEY);
|
|
134
|
+
const contract = getAuctionContract(signer);
|
|
135
|
+
|
|
136
|
+
const value = utils.parseEther(params.valueEth);
|
|
137
|
+
const salt = params.saltHex ?? utils.randomBytes(32);
|
|
138
|
+
|
|
139
|
+
const tx = await contract.revealLock(
|
|
140
|
+
params.auctionId,
|
|
141
|
+
params.lockId,
|
|
142
|
+
value,
|
|
143
|
+
salt
|
|
144
|
+
);
|
|
145
|
+
await tx.wait();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function submitMatchResults(
|
|
149
|
+
params: SubmitMatchResultsParams
|
|
150
|
+
): Promise<void> {
|
|
151
|
+
const signer = getSigner(RPC_URL, PRIVATE_KEY);
|
|
152
|
+
const contract = getAuctionContract(signer);
|
|
153
|
+
|
|
154
|
+
const finalValues = params.finalValuesEth.map((v) =>
|
|
155
|
+
utils.parseEther(v)
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const tx = await contract.submitMatchResults(
|
|
159
|
+
params.auctionId,
|
|
160
|
+
params.lockIds,
|
|
161
|
+
params.bidders,
|
|
162
|
+
finalValues
|
|
163
|
+
);
|
|
164
|
+
await tx.wait();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function withdrawMatchResult(
|
|
168
|
+
params: WithdrawMatchResultParams
|
|
169
|
+
): Promise<void> {
|
|
170
|
+
const signer = getSigner(RPC_URL, PRIVATE_KEY);
|
|
171
|
+
const contract = getAuctionContract(signer);
|
|
172
|
+
|
|
173
|
+
const tx = await contract.withdrawMatchResult(
|
|
174
|
+
params.auctionId,
|
|
175
|
+
params.lockId
|
|
176
|
+
);
|
|
177
|
+
await tx.wait();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function getLockInfo(
|
|
181
|
+
auctionId: string,
|
|
182
|
+
lockId: string
|
|
183
|
+
): Promise<{ exists: boolean; revealTime: bigint; secret: string }> {
|
|
184
|
+
const signer = getSigner(RPC_URL, PRIVATE_KEY);
|
|
185
|
+
const contract = getAuctionContract(signer);
|
|
186
|
+
|
|
187
|
+
const result = await contract.getLockInfo(auctionId, lockId);
|
|
188
|
+
return {
|
|
189
|
+
exists: result[0],
|
|
190
|
+
revealTime: result[1].toBigInt(),
|
|
191
|
+
secret: result[2],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function getMatchResult(
|
|
196
|
+
auctionId: string,
|
|
197
|
+
bidder: string
|
|
198
|
+
): Promise<{ lockId: string; isChallenged: boolean; isWithdrawn: boolean }> {
|
|
199
|
+
const signer = getSigner(RPC_URL, PRIVATE_KEY);
|
|
200
|
+
const contract = getAuctionContract(signer);
|
|
201
|
+
|
|
202
|
+
const result = await contract.getMatchResult(auctionId, bidder);
|
|
203
|
+
return {
|
|
204
|
+
lockId: result[0],
|
|
205
|
+
isChallenged: result[1],
|
|
206
|
+
isWithdrawn: result[2],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
|
|
2
|
+
import { getAuctionID } from "./serialize";
|
|
3
|
+
import {
|
|
4
|
+
CreateAuctionConfigParams,
|
|
5
|
+
SetUnlockStrategyParams,
|
|
6
|
+
CreateAuctionParams,
|
|
7
|
+
UnlockTokensParams,
|
|
8
|
+
} from "./type";
|
|
9
|
+
import {getSigner} from "../lib/signer";
|
|
10
|
+
import {RPC_URL, PRIVATE_KEY,AUCTION_ADDR,AUCTION_ABI} from "../../config/auction.config";
|
|
11
|
+
import {Wallet,Contract,providers,Signer, utils} from "ethers";
|
|
12
|
+
import { MatchResultWithdrawnEvent } from "./event";
|
|
13
|
+
|
|
14
|
+
async function getChainYContract(signer:Wallet) {
|
|
15
|
+
const abi= AUCTION_ABI.chainYVault.abi;
|
|
16
|
+
return new Contract(AUCTION_ADDR.chainYVault, abi, signer);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export function getProvider(): providers.JsonRpcProvider {
|
|
21
|
+
return new providers.JsonRpcProvider(RPC_URL);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function toWei(valueEth: string): bigint {
|
|
25
|
+
return utils.parseEther(valueEth).toBigInt();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
// 创建 Auction 配置
|
|
30
|
+
export async function createAuctionConfig({
|
|
31
|
+
auctionType,
|
|
32
|
+
baseAmountEth,
|
|
33
|
+
extendSeconds,
|
|
34
|
+
}: CreateAuctionConfigParams): Promise<string> {
|
|
35
|
+
const signer = getSigner(RPC_URL,PRIVATE_KEY);
|
|
36
|
+
const vault = await getChainYContract(signer);
|
|
37
|
+
|
|
38
|
+
const tx = await vault.createAuctionConfig(
|
|
39
|
+
auctionType,
|
|
40
|
+
toWei(baseAmountEth),
|
|
41
|
+
extendSeconds
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
await tx.wait();
|
|
45
|
+
return tx.hash;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 创建Auction
|
|
49
|
+
export async function createAuction({
|
|
50
|
+
configId,
|
|
51
|
+
secretText,
|
|
52
|
+
expirationTs,
|
|
53
|
+
amountEth,
|
|
54
|
+
}: CreateAuctionParams): Promise<{
|
|
55
|
+
txHash: string;
|
|
56
|
+
auctionId?: string;
|
|
57
|
+
lockId?: string;
|
|
58
|
+
}> {
|
|
59
|
+
const signer = getSigner(RPC_URL,PRIVATE_KEY);
|
|
60
|
+
const vault = await getChainYContract(signer);
|
|
61
|
+
const hashSecret = utils.keccak256(
|
|
62
|
+
utils.toUtf8Bytes(secretText)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const tx = await vault.createAuction(
|
|
66
|
+
configId,
|
|
67
|
+
hashSecret,
|
|
68
|
+
expirationTs,
|
|
69
|
+
{ value: toWei(amountEth) }
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const receipt = await tx.wait();
|
|
73
|
+
const evt = receipt.events?.find(
|
|
74
|
+
(e:any) => e.event === "TokensLocked"
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
txHash: tx.hash,
|
|
79
|
+
auctionId: evt?.args?.auctionId,
|
|
80
|
+
lockId: evt?.args?.lockId,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 解锁Token
|
|
85
|
+
export async function unlockTokens({
|
|
86
|
+
lockId,
|
|
87
|
+
rawReceipt,
|
|
88
|
+
}: UnlockTokensParams): Promise<{
|
|
89
|
+
txHash: string;
|
|
90
|
+
recipient?: string;
|
|
91
|
+
amount?: bigint;
|
|
92
|
+
}> {
|
|
93
|
+
const signer = getSigner(RPC_URL,PRIVATE_KEY);
|
|
94
|
+
const vault = await getChainYContract(signer);
|
|
95
|
+
|
|
96
|
+
const tx = await vault.unlockTokens(lockId, rawReceipt);
|
|
97
|
+
const receipt = await tx.wait();
|
|
98
|
+
|
|
99
|
+
const evt = receipt.events?.find(
|
|
100
|
+
(e:any) => e.event === "TokensUnlocked"
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
txHash: tx.hash,
|
|
105
|
+
recipient: evt?.args?.recipient,
|
|
106
|
+
amount: evt?.args?.amount,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 查询拍卖信息
|
|
111
|
+
export async function getAuctionInfo(auctionId: string | number): Promise<{
|
|
112
|
+
auctionType: number;
|
|
113
|
+
baseAmount: bigint;
|
|
114
|
+
revealTime: bigint;
|
|
115
|
+
}> {
|
|
116
|
+
const signer = getSigner(RPC_URL, PRIVATE_KEY);
|
|
117
|
+
const vault = await getChainYContract(signer);
|
|
118
|
+
|
|
119
|
+
const result = await vault.getAuctionInfo(auctionId);
|
|
120
|
+
return {
|
|
121
|
+
auctionType: result[0],
|
|
122
|
+
baseAmount: result[1].toBigInt(),
|
|
123
|
+
revealTime: result[2].toBigInt(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 查询锁仓信息
|
|
128
|
+
export async function getAuctionLockInfo(lockId: string): Promise<{
|
|
129
|
+
owner: string;
|
|
130
|
+
amount: bigint;
|
|
131
|
+
isLocked: boolean;
|
|
132
|
+
revealTime: bigint;
|
|
133
|
+
}> {
|
|
134
|
+
const signer = getSigner(RPC_URL, PRIVATE_KEY);
|
|
135
|
+
const vault = await getChainYContract(signer);
|
|
136
|
+
|
|
137
|
+
const result = await vault.getAuctionLockInfo(lockId);
|
|
138
|
+
return {
|
|
139
|
+
owner: result[0],
|
|
140
|
+
amount: result[1].toBigInt(),
|
|
141
|
+
isLocked: result[2],
|
|
142
|
+
revealTime: result[3].toBigInt(),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 获取当前激活拍卖数量
|
|
147
|
+
export async function getActiveAuctionsCount(): Promise<bigint> {
|
|
148
|
+
const signer = getSigner(RPC_URL, PRIVATE_KEY);
|
|
149
|
+
const vault = await getChainYContract(signer);
|
|
150
|
+
const count = await vault.getActiveAuctionsCount();
|
|
151
|
+
return count.toBigInt();
|
|
152
|
+
}
|
|
153
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
// * ChainXAuction 相关事件
|
|
3
|
+
|
|
4
|
+
export interface MatchResultWithdrawnEvent{
|
|
5
|
+
auctionId: bigint;
|
|
6
|
+
lockId: string;
|
|
7
|
+
recipient: string;
|
|
8
|
+
amount: bigint;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
// * ChainYVault 相关事件
|
|
13
|
+
// event AuctionCreated(uint256 indexed auctionId, uint32 auctionType, uint256 activeAuctionCount, uint256 revealTime
|
|
14
|
+
export interface auctionCreatedEvent {
|
|
15
|
+
auctionId: string; // 使用string存储,auctionId是Keccak256计算的hash值,在调用合约时候会自动转化为uiny256
|
|
16
|
+
auctionType: number;
|
|
17
|
+
activeAuctionCount: bigint;
|
|
18
|
+
revealTime: bigint;
|
|
19
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { ethers } from "hardhat";
|
|
2
|
+
import { Wallet } from "ethers";
|
|
3
|
+
import { MatchResultWithdrawnEvent,auctionCreatedEvent} from "./event";
|
|
4
|
+
import { utils } from "ethers";
|
|
5
|
+
import { AUCTION_ADDR } from "../../config/auction.config";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export function buildAuctionCreatedReceipt(
|
|
9
|
+
event: auctionCreatedEvent,
|
|
10
|
+
status: number = 0x01,
|
|
11
|
+
gasUsed: bigint = BigInt(0),
|
|
12
|
+
gasPrice: bigint = BigInt(0),
|
|
13
|
+
) {
|
|
14
|
+
// * 随意填写的数据
|
|
15
|
+
const statusHex = ethers.utils.hexlify(status);
|
|
16
|
+
const gasUsedHex = ethers.utils.hexlify(gasUsed);
|
|
17
|
+
const gasPriceHex = ethers.utils.hexlify(gasPrice);
|
|
18
|
+
|
|
19
|
+
// 构造Event,实际上只需要Event字段是合法的就可以,并且确保在数组的index=3.
|
|
20
|
+
const topic0 = ethers.utils.id("AuctionCreated(uint256,uint32,uint256,uint256)");
|
|
21
|
+
const topic1 = event.auctionId;
|
|
22
|
+
const eventData = ethers.utils.defaultAbiCoder.encode(
|
|
23
|
+
["uint32", "uint256", "uint256"],
|
|
24
|
+
[event.auctionType, event.activeAuctionCount, event.revealTime]
|
|
25
|
+
);
|
|
26
|
+
const log = [
|
|
27
|
+
AUCTION_ADDR.chainYVault,
|
|
28
|
+
[topic0, topic1],
|
|
29
|
+
ethers.utils.arrayify(eventData),
|
|
30
|
+
];
|
|
31
|
+
const logBlob = ethers.utils.RLP.encode(log);
|
|
32
|
+
|
|
33
|
+
// recipt每个元素必须都是 Hex-string
|
|
34
|
+
const receipt = [statusHex, gasUsedHex, gasPriceHex, logBlob];
|
|
35
|
+
return ethers.utils.RLP.encode(receipt);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 根据 Auciton 所在合约的信息构造AuctionID
|
|
39
|
+
export function getAuctionID(seller: string, sourceChainId: number, vaultAddress: string, activeAuctionCount: bigint): string {
|
|
40
|
+
return ethers.utils.keccak256(
|
|
41
|
+
ethers.utils.solidityPack(
|
|
42
|
+
["address", "uint256", "address", "uint256"],
|
|
43
|
+
[seller, sourceChainId, vaultAddress, activeAuctionCount]
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 构造 rawTx: legacy 格式 [nonce, gasPrice, gasLimit, to, value, data, v, r, s]
|
|
49
|
+
export function buildXTx(
|
|
50
|
+
nonce: number,
|
|
51
|
+
gasPrice: bigint,
|
|
52
|
+
gasLimit: bigint,
|
|
53
|
+
to: string,
|
|
54
|
+
value: bigint,
|
|
55
|
+
data: string,
|
|
56
|
+
v: bigint,
|
|
57
|
+
r: string,
|
|
58
|
+
s: string,
|
|
59
|
+
) {
|
|
60
|
+
return ethers.utils.RLP.encode([
|
|
61
|
+
nonce,
|
|
62
|
+
gasPrice,
|
|
63
|
+
gasLimit,
|
|
64
|
+
to,
|
|
65
|
+
value,
|
|
66
|
+
ethers.utils.arrayify(data),
|
|
67
|
+
v,
|
|
68
|
+
ethers.utils.zeroPad(ethers.utils.arrayify(r), 32),
|
|
69
|
+
ethers.utils.zeroPad(ethers.utils.arrayify(s), 32),
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
interface BuildAuctionCreatedTxParams {
|
|
75
|
+
nonce?: number;
|
|
76
|
+
gasPrice?: bigint;
|
|
77
|
+
gasLimit?: bigint;
|
|
78
|
+
to?: string;
|
|
79
|
+
value?: bigint;
|
|
80
|
+
data?: string;
|
|
81
|
+
chainId?: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function buildAuctionCreatedTx({
|
|
85
|
+
nonce = 0,
|
|
86
|
+
gasPrice = BigInt(0),
|
|
87
|
+
gasLimit = BigInt(0),
|
|
88
|
+
to = "0x",
|
|
89
|
+
value = BigInt(0),
|
|
90
|
+
data = "0x",
|
|
91
|
+
chainId=31337,
|
|
92
|
+
}: BuildAuctionCreatedTxParams
|
|
93
|
+
){
|
|
94
|
+
const tx = {
|
|
95
|
+
nonce,
|
|
96
|
+
gasPrice,
|
|
97
|
+
gasLimit,
|
|
98
|
+
to,
|
|
99
|
+
value,
|
|
100
|
+
data,
|
|
101
|
+
chainId,
|
|
102
|
+
type: 0, // Legacy tx
|
|
103
|
+
};
|
|
104
|
+
return tx
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 构造 Unlock Receipt(RLP)
|
|
110
|
+
*/
|
|
111
|
+
export function buildChainXUnlockReceipt({
|
|
112
|
+
auctionId,
|
|
113
|
+
lockId,
|
|
114
|
+
recipient,
|
|
115
|
+
amount,
|
|
116
|
+
}: MatchResultWithdrawnEvent): string {
|
|
117
|
+
const status = "0x01";
|
|
118
|
+
const cumulativeGasUsed = "0x00";
|
|
119
|
+
const logsBloom = "0x" + "00".repeat(256);
|
|
120
|
+
const logAddress = AUCTION_ADDR.chainXAuction;
|
|
121
|
+
|
|
122
|
+
const topic0 = utils.id(
|
|
123
|
+
"MatchResultWithdrawn(uint256,bytes32,address,uint256)"
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const eventData = utils.defaultAbiCoder.encode(
|
|
127
|
+
["uint256", "bytes32", "address", "uint256"],
|
|
128
|
+
[auctionId, lockId, recipient, amount]
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const log = [
|
|
132
|
+
logAddress,
|
|
133
|
+
[topic0],
|
|
134
|
+
utils.arrayify(eventData),
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
const logBlob = utils.RLP.encode(log);
|
|
138
|
+
return utils.RLP.encode([
|
|
139
|
+
status,
|
|
140
|
+
cumulativeGasUsed,
|
|
141
|
+
logsBloom,
|
|
142
|
+
logBlob,
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 仅在直接运行本文件时演示生成 rawReceipt,避免被 require 时产生副作用
|
|
147
|
+
if (require.main === module) {
|
|
148
|
+
const stauts = 0x01;
|
|
149
|
+
const gasUsed = BigInt(21000);
|
|
150
|
+
const gasPrice = BigInt(21000);
|
|
151
|
+
const sourceChainId = 11223344;
|
|
152
|
+
|
|
153
|
+
const auctionType = 0x00000001;
|
|
154
|
+
const seller = "0x5F40D0aA63957D41ec56500c5dc61c28Ae866a5e";
|
|
155
|
+
const activeAuctionCount = BigInt(0);
|
|
156
|
+
const auctionId = getAuctionID(seller, sourceChainId, seller, activeAuctionCount);
|
|
157
|
+
const revealTime = BigInt(Math.floor(Date.now() / 1000) + 3600);
|
|
158
|
+
const logAddress = "0x307833383843383138434138423932353162333933313331433038613733364136376363423139323937";
|
|
159
|
+
|
|
160
|
+
const rowReceipt = buildAuctionCreatedReceipt({ auctionId, auctionType, activeAuctionCount, revealTime })
|
|
161
|
+
console.log("rawReceipt:", rowReceipt);
|
|
162
|
+
}
|