@atomiqlabs/chain-starknet 2.0.1 → 3.0.0-beta.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/dist/starknet/StarknetInitializer.d.ts +1 -1
- package/dist/starknet/StarknetInitializer.js +6 -1
- package/dist/starknet/btcrelay/StarknetBtcRelay.js +5 -5
- package/dist/starknet/chain/StarknetAction.d.ts +2 -10
- package/dist/starknet/chain/StarknetAction.js +21 -21
- package/dist/starknet/chain/modules/StarknetAccounts.js +1 -1
- package/dist/starknet/chain/modules/StarknetFees.d.ts +30 -8
- package/dist/starknet/chain/modules/StarknetFees.js +40 -26
- package/dist/starknet/chain/modules/StarknetSignatures.js +1 -1
- package/dist/starknet/chain/modules/StarknetTokens.d.ts +6 -4
- package/dist/starknet/chain/modules/StarknetTokens.js +2 -2
- package/dist/starknet/spv_swap/StarknetSpvVaultContract.js +6 -6
- package/dist/starknet/swaps/EscrowManagerAbi.d.ts +29 -0
- package/dist/starknet/swaps/EscrowManagerAbi.js +40 -0
- package/dist/starknet/swaps/StarknetSwapContract.js +2 -2
- package/dist/starknet/swaps/StarknetSwapData.js +10 -1
- package/dist/starknet/swaps/handlers/IHandler.d.ts +1 -1
- package/dist/starknet/swaps/handlers/claim/ClaimHandlers.d.ts +1 -1
- package/dist/starknet/swaps/handlers/claim/HashlockClaimHandler.d.ts +1 -1
- package/dist/starknet/swaps/handlers/claim/HashlockClaimHandler.js +1 -1
- package/dist/starknet/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.d.ts +1 -1
- package/dist/starknet/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.js +1 -1
- package/dist/starknet/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.d.ts +1 -1
- package/dist/starknet/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.js +1 -1
- package/dist/starknet/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.d.ts +1 -1
- package/dist/starknet/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.js +1 -1
- package/dist/starknet/swaps/handlers/claim/btc/IBitcoinClaimHandler.d.ts +1 -1
- package/dist/starknet/swaps/handlers/claim/btc/IBitcoinClaimHandler.js +1 -1
- package/dist/starknet/swaps/handlers/refund/TimelockRefundHandler.d.ts +1 -1
- package/dist/starknet/swaps/handlers/refund/TimelockRefundHandler.js +1 -1
- package/dist/starknet/swaps/modules/StarknetLpVault.js +2 -2
- package/dist/starknet/swaps/modules/StarknetSwapClaim.js +5 -5
- package/dist/starknet/swaps/modules/StarknetSwapInit.js +41 -5
- package/dist/starknet/swaps/modules/StarknetSwapRefund.js +4 -4
- package/dist/utils/Utils.js +3 -3
- package/package.json +2 -2
- package/src/starknet/StarknetInitializer.ts +7 -2
- package/src/starknet/btcrelay/StarknetBtcRelay.ts +9 -6
- package/src/starknet/chain/StarknetAction.ts +15 -20
- package/src/starknet/chain/modules/StarknetAccounts.ts +1 -1
- package/src/starknet/chain/modules/StarknetFees.ts +53 -31
- package/src/starknet/chain/modules/StarknetSignatures.ts +5 -4
- package/src/starknet/chain/modules/StarknetTokens.ts +2 -2
- package/src/starknet/spv_swap/StarknetSpvVaultContract.ts +6 -6
- package/src/starknet/swaps/EscrowManagerAbi.ts +40 -0
- package/src/starknet/swaps/StarknetSwapContract.ts +2 -2
- package/src/starknet/swaps/StarknetSwapData.ts +11 -2
- package/src/starknet/swaps/handlers/IHandler.ts +1 -1
- package/src/starknet/swaps/handlers/claim/ClaimHandlers.ts +1 -1
- package/src/starknet/swaps/handlers/claim/HashlockClaimHandler.ts +2 -2
- package/src/starknet/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.ts +2 -2
- package/src/starknet/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.ts +2 -2
- package/src/starknet/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.ts +2 -2
- package/src/starknet/swaps/handlers/claim/btc/IBitcoinClaimHandler.ts +2 -2
- package/src/starknet/swaps/handlers/refund/TimelockRefundHandler.ts +3 -3
- package/src/starknet/swaps/modules/StarknetLpVault.ts +2 -2
- package/src/starknet/swaps/modules/StarknetSwapClaim.ts +7 -7
- package/src/starknet/swaps/modules/StarknetSwapInit.ts +42 -7
- package/src/starknet/swaps/modules/StarknetSwapRefund.ts +6 -7
- package/src/utils/Utils.ts +1 -1
|
@@ -37,4 +37,4 @@ class BitcoinOutputClaimHandler extends IBitcoinClaimHandler_1.IBitcoinClaimHand
|
|
|
37
37
|
}
|
|
38
38
|
exports.BitcoinOutputClaimHandler = BitcoinOutputClaimHandler;
|
|
39
39
|
BitcoinOutputClaimHandler.type = base_1.ChainSwapType.CHAIN;
|
|
40
|
-
BitcoinOutputClaimHandler.gas = {
|
|
40
|
+
BitcoinOutputClaimHandler.gas = { l1DataGas: 0, l2Gas: 20000 * 40000, l1Gas: 0 };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { StarknetSwapData } from "../../../StarknetSwapData";
|
|
2
|
-
import { StarknetGas } from "../../../../chain/StarknetAction";
|
|
3
2
|
import { ChainSwapType } from "@atomiqlabs/base";
|
|
4
3
|
import { BigNumberish } from "starknet";
|
|
5
4
|
import { StarknetTx } from "../../../../chain/modules/StarknetTransactions";
|
|
6
5
|
import { BitcoinCommitmentData, BitcoinWitnessData, IBitcoinClaimHandler } from "./IBitcoinClaimHandler";
|
|
6
|
+
import { StarknetGas } from "../../../../chain/modules/StarknetFees";
|
|
7
7
|
export type BitcoinTxIdCommitmentData = {
|
|
8
8
|
txId: string;
|
|
9
9
|
};
|
|
@@ -27,4 +27,4 @@ class BitcoinTxIdClaimHandler extends IBitcoinClaimHandler_1.IBitcoinClaimHandle
|
|
|
27
27
|
}
|
|
28
28
|
exports.BitcoinTxIdClaimHandler = BitcoinTxIdClaimHandler;
|
|
29
29
|
BitcoinTxIdClaimHandler.type = base_1.ChainSwapType.CHAIN_TXID;
|
|
30
|
-
BitcoinTxIdClaimHandler.gas = {
|
|
30
|
+
BitcoinTxIdClaimHandler.gas = { l1DataGas: 0, l2Gas: 20000 * 40000, l1Gas: 0 };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { IClaimHandler } from "../ClaimHandlers";
|
|
2
2
|
import { StarknetSwapData } from "../../../StarknetSwapData";
|
|
3
|
-
import { StarknetGas } from "../../../../chain/StarknetAction";
|
|
4
3
|
import { ChainSwapType, RelaySynchronizer } from "@atomiqlabs/base";
|
|
5
4
|
import { BigNumberish } from "starknet";
|
|
6
5
|
import { StarknetBtcStoredHeader } from "../../../../btcrelay/headers/StarknetBtcStoredHeader";
|
|
7
6
|
import { StarknetTx } from "../../../../chain/modules/StarknetTransactions";
|
|
8
7
|
import { StarknetBtcRelay } from "../../../../btcrelay/StarknetBtcRelay";
|
|
8
|
+
import { StarknetGas } from "../../../../chain/modules/StarknetFees";
|
|
9
9
|
export type BitcoinCommitmentData = {
|
|
10
10
|
btcRelay: StarknetBtcRelay<any>;
|
|
11
11
|
confirmations: number;
|
|
@@ -49,4 +49,4 @@ class IBitcoinClaimHandler {
|
|
|
49
49
|
exports.IBitcoinClaimHandler = IBitcoinClaimHandler;
|
|
50
50
|
IBitcoinClaimHandler.address = "";
|
|
51
51
|
IBitcoinClaimHandler.type = base_1.ChainSwapType.CHAIN_TXID;
|
|
52
|
-
IBitcoinClaimHandler.gas = {
|
|
52
|
+
IBitcoinClaimHandler.gas = { l1DataGas: 0, l2Gas: 20000 * 40000, l1Gas: 0 };
|
|
@@ -2,7 +2,7 @@ import { StarknetTx } from "../../../chain/modules/StarknetTransactions";
|
|
|
2
2
|
import { StarknetSwapData } from "../../StarknetSwapData";
|
|
3
3
|
import { BigNumberish } from "starknet";
|
|
4
4
|
import { IHandler } from "../IHandler";
|
|
5
|
-
import { StarknetGas } from "../../../chain/
|
|
5
|
+
import { StarknetGas } from "../../../chain/modules/StarknetFees";
|
|
6
6
|
export declare class TimelockRefundHandler implements IHandler<bigint, never> {
|
|
7
7
|
readonly address: string;
|
|
8
8
|
static readonly gas: StarknetGas;
|
|
@@ -117,6 +117,6 @@ class StarknetLpVault extends StarknetSwapModule_1.StarknetSwapModule {
|
|
|
117
117
|
}
|
|
118
118
|
exports.StarknetLpVault = StarknetLpVault;
|
|
119
119
|
StarknetLpVault.GasCosts = {
|
|
120
|
-
WITHDRAW: {
|
|
121
|
-
DEPOSIT: { l1:
|
|
120
|
+
WITHDRAW: { l1DataGas: 500, l2Gas: 3200000, l1Gas: 0 },
|
|
121
|
+
DEPOSIT: { l1: 500, l2Gas: 4000000, l1Gas: 0 }
|
|
122
122
|
};
|
|
@@ -18,7 +18,7 @@ class StarknetSwapClaim extends StarknetSwapModule_1.StarknetSwapModule {
|
|
|
18
18
|
* @private
|
|
19
19
|
*/
|
|
20
20
|
Claim(signer, swapData, witness, claimHandlerGas) {
|
|
21
|
-
return new StarknetAction_1.StarknetAction(signer, this.root, this.swapContract.populateTransaction.claim(swapData.toEscrowStruct(), witness), (0,
|
|
21
|
+
return new StarknetAction_1.StarknetAction(signer, this.root, this.swapContract.populateTransaction.claim(swapData.toEscrowStruct(), witness), (0, StarknetFees_1.starknetGasAdd)(swapData.payOut ? StarknetSwapClaim.GasCosts.CLAIM_PAY_OUT : StarknetSwapClaim.GasCosts.CLAIM, claimHandlerGas));
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Creates transactions claiming the swap using a secret (for HTLC swaps)
|
|
@@ -89,12 +89,12 @@ class StarknetSwapClaim extends StarknetSwapModule_1.StarknetSwapModule {
|
|
|
89
89
|
let gasRequired = swapData.payOut ? StarknetSwapClaim.GasCosts.CLAIM_PAY_OUT : StarknetSwapClaim.GasCosts.CLAIM;
|
|
90
90
|
const claimHandler = this.contract.claimHandlersByAddress[swapData.claimHandler.toLowerCase()];
|
|
91
91
|
if (claimHandler != null)
|
|
92
|
-
gasRequired = (0,
|
|
93
|
-
return StarknetFees_1.StarknetFees.getGasFee(gasRequired
|
|
92
|
+
gasRequired = (0, StarknetFees_1.starknetGasAdd)(gasRequired, claimHandler.getGas(swapData));
|
|
93
|
+
return StarknetFees_1.StarknetFees.getGasFee(gasRequired, feeRate);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
exports.StarknetSwapClaim = StarknetSwapClaim;
|
|
97
97
|
StarknetSwapClaim.GasCosts = {
|
|
98
|
-
CLAIM: {
|
|
99
|
-
CLAIM_PAY_OUT: {
|
|
98
|
+
CLAIM: { l1DataGas: 750, l2Gas: 4000000, l1Gas: 0 },
|
|
99
|
+
CLAIM_PAY_OUT: { l1DataGas: 900, l2Gas: 6000000, l1Gas: 0 }
|
|
100
100
|
};
|
|
@@ -6,10 +6,24 @@ const Utils_1 = require("../../../utils/Utils");
|
|
|
6
6
|
const buffer_1 = require("buffer");
|
|
7
7
|
const StarknetAction_1 = require("../../chain/StarknetAction");
|
|
8
8
|
const StarknetSwapModule_1 = require("../StarknetSwapModule");
|
|
9
|
+
const starknet_1 = require("starknet");
|
|
9
10
|
const StarknetFees_1 = require("../../chain/modules/StarknetFees");
|
|
10
11
|
const Initialize = [
|
|
11
12
|
{ name: 'Swap hash', type: 'felt' },
|
|
12
|
-
{ name: '
|
|
13
|
+
{ name: 'Offerer', type: 'ContractAddress' },
|
|
14
|
+
{ name: 'Claimer', type: 'ContractAddress' },
|
|
15
|
+
{ name: 'Token amount', type: 'TokenAmount' },
|
|
16
|
+
{ name: 'Pay in', type: 'bool' },
|
|
17
|
+
{ name: 'Pay out', type: 'bool' },
|
|
18
|
+
{ name: 'Tracking reputation', type: 'bool' },
|
|
19
|
+
{ name: 'Claim handler', type: 'ContractAddress' },
|
|
20
|
+
{ name: 'Claim data', type: 'felt' },
|
|
21
|
+
{ name: 'Refund handler', type: 'ContractAddress' },
|
|
22
|
+
{ name: 'Refund data', type: 'felt' },
|
|
23
|
+
{ name: 'Security deposit', type: 'TokenAmount' },
|
|
24
|
+
{ name: 'Claimer bounty', type: 'TokenAmount' },
|
|
25
|
+
{ name: 'Claim action hash', type: 'felt' },
|
|
26
|
+
{ name: 'Deadline', type: 'timestamp' }
|
|
13
27
|
];
|
|
14
28
|
class StarknetSwapInit extends StarknetSwapModule_1.StarknetSwapModule {
|
|
15
29
|
/**
|
|
@@ -51,7 +65,29 @@ class StarknetSwapInit extends StarknetSwapModule_1.StarknetSwapModule {
|
|
|
51
65
|
const authTimeout = Math.floor(Date.now() / 1000) + authorizationTimeout;
|
|
52
66
|
const signature = await this.root.Signatures.signTypedMessage(signer, Initialize, "Initialize", {
|
|
53
67
|
"Swap hash": "0x" + swapData.getEscrowHash(),
|
|
54
|
-
"
|
|
68
|
+
"Offerer": swapData.offerer,
|
|
69
|
+
"Claimer": swapData.claimer,
|
|
70
|
+
"Token amount": {
|
|
71
|
+
token_address: swapData.token,
|
|
72
|
+
amount: starknet_1.cairo.uint256(swapData.amount)
|
|
73
|
+
},
|
|
74
|
+
"Pay in": swapData.isPayIn(),
|
|
75
|
+
"Pay out": swapData.isPayOut(),
|
|
76
|
+
"Tracking reputation": swapData.reputation,
|
|
77
|
+
"Refund handler": swapData.refundHandler,
|
|
78
|
+
"Claim handler": swapData.claimHandler,
|
|
79
|
+
"Claim data": "0x" + swapData.getClaimHash(),
|
|
80
|
+
"Refund data": swapData.refundData.startsWith("0x") ? swapData.refundData : "0x" + swapData.refundData,
|
|
81
|
+
"Security deposit": {
|
|
82
|
+
token_address: swapData.feeToken,
|
|
83
|
+
amount: starknet_1.cairo.uint256(swapData.securityDeposit)
|
|
84
|
+
},
|
|
85
|
+
"Claimer bounty": {
|
|
86
|
+
token_address: swapData.feeToken,
|
|
87
|
+
amount: starknet_1.cairo.uint256(swapData.claimerBounty)
|
|
88
|
+
},
|
|
89
|
+
"Claim action hash": 0n,
|
|
90
|
+
"Deadline": (0, Utils_1.toHex)(authTimeout)
|
|
55
91
|
});
|
|
56
92
|
return {
|
|
57
93
|
prefix: this.getAuthPrefix(swapData),
|
|
@@ -154,11 +190,11 @@ class StarknetSwapInit extends StarknetSwapModule_1.StarknetSwapModule {
|
|
|
154
190
|
*/
|
|
155
191
|
async getInitFee(swapData, feeRate) {
|
|
156
192
|
feeRate ?? (feeRate = await this.root.Fees.getFeeRate());
|
|
157
|
-
return StarknetFees_1.StarknetFees.getGasFee(swapData.payIn ? StarknetSwapInit.GasCosts.INIT_PAY_IN
|
|
193
|
+
return StarknetFees_1.StarknetFees.getGasFee(swapData.payIn ? StarknetSwapInit.GasCosts.INIT_PAY_IN : StarknetSwapInit.GasCosts.INIT, feeRate);
|
|
158
194
|
}
|
|
159
195
|
}
|
|
160
196
|
exports.StarknetSwapInit = StarknetSwapInit;
|
|
161
197
|
StarknetSwapInit.GasCosts = {
|
|
162
|
-
INIT: {
|
|
163
|
-
INIT_PAY_IN: {
|
|
198
|
+
INIT: { l1DataGas: 750, l2Gas: 8000000, l1Gas: 0 },
|
|
199
|
+
INIT_PAY_IN: { l1DataGas: 500, l2Gas: 4800000, l1Gas: 0 },
|
|
164
200
|
};
|
|
@@ -22,7 +22,7 @@ class StarknetSwapRefund extends StarknetSwapModule_1.StarknetSwapModule {
|
|
|
22
22
|
* @private
|
|
23
23
|
*/
|
|
24
24
|
Refund(signer, swapData, witness, handlerGas) {
|
|
25
|
-
return new StarknetAction_1.StarknetAction(signer, this.root, this.swapContract.populateTransaction.refund(swapData.toEscrowStruct(), witness), (0,
|
|
25
|
+
return new StarknetAction_1.StarknetAction(signer, this.root, this.swapContract.populateTransaction.refund(swapData.toEscrowStruct(), witness), (0, StarknetFees_1.starknetGasAdd)(swapData.payIn ? StarknetSwapRefund.GasCosts.REFUND_PAY_OUT : StarknetSwapRefund.GasCosts.REFUND, handlerGas));
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Action for cooperative refunding with signature
|
|
@@ -118,11 +118,11 @@ class StarknetSwapRefund extends StarknetSwapModule_1.StarknetSwapModule {
|
|
|
118
118
|
*/
|
|
119
119
|
async getRefundFee(swapData, feeRate) {
|
|
120
120
|
feeRate ?? (feeRate = await this.root.Fees.getFeeRate());
|
|
121
|
-
return StarknetFees_1.StarknetFees.getGasFee(swapData.payIn ? StarknetSwapRefund.GasCosts.REFUND_PAY_OUT
|
|
121
|
+
return StarknetFees_1.StarknetFees.getGasFee(swapData.payIn ? StarknetSwapRefund.GasCosts.REFUND_PAY_OUT : StarknetSwapRefund.GasCosts.REFUND, feeRate);
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
exports.StarknetSwapRefund = StarknetSwapRefund;
|
|
125
125
|
StarknetSwapRefund.GasCosts = {
|
|
126
|
-
REFUND: {
|
|
127
|
-
REFUND_PAY_OUT: {
|
|
126
|
+
REFUND: { l1DataGas: 750, l2Gas: 4000000, l1Gas: 0 },
|
|
127
|
+
REFUND_PAY_OUT: { l1DataGas: 900, l2Gas: 6000000, l1Gas: 0 }
|
|
128
128
|
};
|
package/dist/utils/Utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.findLastIndex = exports.parseInitFunctionCalldata = exports.poseidonHashRange = exports.bufferToByteArray = exports.bufferToBytes31Span = exports.bytes31SpanToBuffer = exports.toBigInt = exports.bigNumberishToBuffer = exports.u32ReverseEndianness = exports.bufferToU32Array = exports.u32ArrayToBuffer = exports.calculateHash = exports.toHex = exports.tryWithRetries = exports.getLogger = exports.onceAsync = exports.timeoutPromise = exports.isUint256 = void 0;
|
|
4
|
-
const
|
|
4
|
+
const starknet_types_08_1 = require("@starknet-io/starknet-types-08");
|
|
5
5
|
const starknet_1 = require("starknet");
|
|
6
6
|
const buffer_1 = require("buffer");
|
|
7
7
|
const StarknetSwapData_1 = require("../starknet/swaps/StarknetSwapData");
|
|
@@ -94,8 +94,8 @@ function calculateHash(tx) {
|
|
|
94
94
|
chainId: tx.details.chainId,
|
|
95
95
|
nonce: tx.details.nonce,
|
|
96
96
|
accountDeploymentData: tx.details.version === "0x3" ? tx.details.accountDeploymentData : null,
|
|
97
|
-
nonceDataAvailabilityMode: tx.details.version === "0x3" ?
|
|
98
|
-
feeDataAvailabilityMode: tx.details.version === "0x3" ?
|
|
97
|
+
nonceDataAvailabilityMode: tx.details.version === "0x3" ? starknet_types_08_1.EDAMode[tx.details.nonceDataAvailabilityMode] : null,
|
|
98
|
+
feeDataAvailabilityMode: tx.details.version === "0x3" ? starknet_types_08_1.EDAMode[tx.details.feeDataAvailabilityMode] : null,
|
|
99
99
|
resourceBounds: tx.details.version === "0x3" ? tx.details.resourceBounds : null,
|
|
100
100
|
tip: tx.details.version === "0x3" ? tx.details.tip : null,
|
|
101
101
|
paymasterData: tx.details.version === "0x3" ? tx.details.paymasterData : null
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atomiqlabs/chain-starknet",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-beta.0",
|
|
4
4
|
"description": "Starknet specific base implementation",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types:": "./dist/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"buffer": "6.0.3"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"starknet": "^
|
|
32
|
+
"starknet": "^7.5.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"typescript": "4.9.5"
|
|
@@ -12,7 +12,7 @@ import {StarknetSpvVaultData} from "./spv_swap/StarknetSpvVaultData";
|
|
|
12
12
|
import {StarknetSpvWithdrawalData} from "./spv_swap/StarknetSpvWithdrawalData";
|
|
13
13
|
import {RpcProviderWithRetries} from "./provider/RpcProviderWithRetries";
|
|
14
14
|
|
|
15
|
-
export type StarknetAssetsType = BaseTokenType<"ETH" | "STRK" | "WBTC">;
|
|
15
|
+
export type StarknetAssetsType = BaseTokenType<"ETH" | "STRK" | "WBTC" | "TBTC">;
|
|
16
16
|
export const StarknetAssets: StarknetAssetsType = {
|
|
17
17
|
ETH: {
|
|
18
18
|
address: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
|
|
@@ -27,6 +27,11 @@ export const StarknetAssets: StarknetAssetsType = {
|
|
|
27
27
|
WBTC: {
|
|
28
28
|
address: "0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac",
|
|
29
29
|
decimals: 8
|
|
30
|
+
},
|
|
31
|
+
TBTC: {
|
|
32
|
+
address: "0x04daa17763b286d1e59b97c283C0b8C949994C361e426A28F743c67bDfE9a32f",
|
|
33
|
+
decimals: 18,
|
|
34
|
+
displayDecimals: 8
|
|
30
35
|
}
|
|
31
36
|
} as const;
|
|
32
37
|
|
|
@@ -51,7 +56,7 @@ export function initializeStarknet(
|
|
|
51
56
|
new RpcProviderWithRetries({nodeUrl: options.rpcUrl}) :
|
|
52
57
|
options.rpcUrl;
|
|
53
58
|
|
|
54
|
-
const Fees = options.fees ?? new StarknetFees(provider
|
|
59
|
+
const Fees = options.fees ?? new StarknetFees(provider);
|
|
55
60
|
|
|
56
61
|
const chainId = options.chainId ??
|
|
57
62
|
(network===BitcoinNetwork.MAINNET ? constants.StarknetChainId.SN_MAIN : constants.StarknetChainId.SN_SEPOLIA);
|
|
@@ -13,7 +13,7 @@ import {StarknetTx} from "../chain/modules/StarknetTransactions";
|
|
|
13
13
|
import {StarknetSigner} from "../wallet/StarknetSigner";
|
|
14
14
|
import {BtcRelayAbi} from "./BtcRelayAbi";
|
|
15
15
|
import {BigNumberish, hash} from "starknet";
|
|
16
|
-
import {StarknetFees} from "../chain/modules/StarknetFees";
|
|
16
|
+
import {StarknetFees, starknetGasAdd, starknetGasMul} from "../chain/modules/StarknetFees";
|
|
17
17
|
import {StarknetChainInterface} from "../chain/StarknetChainInterface";
|
|
18
18
|
import {StarknetAction} from "../chain/StarknetAction";
|
|
19
19
|
|
|
@@ -29,8 +29,8 @@ function serializeBlockHeader(e: BtcBlock): StarknetBtcHeader {
|
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const GAS_PER_BLOCKHEADER =
|
|
33
|
-
const GAS_PER_BLOCKHEADER_FORK = 1000;
|
|
32
|
+
const GAS_PER_BLOCKHEADER = {l1DataGas: 600, l2Gas: 40_000_000, l1Gas: 0};
|
|
33
|
+
const GAS_PER_BLOCKHEADER_FORK = {l1DataGas: 1000, l2Gas: 60_000_000, l1Gas: 0};
|
|
34
34
|
|
|
35
35
|
const btcRelayAddreses = {
|
|
36
36
|
[BitcoinNetwork.TESTNET4]: "0x0099b63f39f0cabb767361de3d8d3e97212351a51540e2687c2571f4da490dbe",
|
|
@@ -62,7 +62,7 @@ export class StarknetBtcRelay<B extends BtcBlock>
|
|
|
62
62
|
entrypoint: "submit_main_blockheaders",
|
|
63
63
|
calldata: serializeCalldata(mainHeaders, storedHeader, [])
|
|
64
64
|
},
|
|
65
|
-
|
|
65
|
+
starknetGasMul(GAS_PER_BLOCKHEADER, mainHeaders.length)
|
|
66
66
|
)
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -73,7 +73,7 @@ export class StarknetBtcRelay<B extends BtcBlock>
|
|
|
73
73
|
entrypoint: "submit_short_fork_blockheaders",
|
|
74
74
|
calldata: serializeCalldata(forkHeaders, storedHeader, [])
|
|
75
75
|
},
|
|
76
|
-
|
|
76
|
+
starknetGasMul(GAS_PER_BLOCKHEADER, forkHeaders.length)
|
|
77
77
|
)
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -84,7 +84,10 @@ export class StarknetBtcRelay<B extends BtcBlock>
|
|
|
84
84
|
entrypoint: "submit_fork_blockheaders",
|
|
85
85
|
calldata: serializeCalldata(forkHeaders, storedHeader, [toHex(forkId)])
|
|
86
86
|
},
|
|
87
|
-
|
|
87
|
+
starknetGasAdd(
|
|
88
|
+
starknetGasMul(GAS_PER_BLOCKHEADER, forkHeaders.length),
|
|
89
|
+
starknetGasMul(GAS_PER_BLOCKHEADER_FORK, totalForkHeaders)
|
|
90
|
+
)
|
|
88
91
|
)
|
|
89
92
|
}
|
|
90
93
|
|
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
import {Call} from "starknet";
|
|
2
2
|
import {StarknetChainInterface} from "./StarknetChainInterface";
|
|
3
3
|
import {StarknetTx} from "./modules/StarknetTransactions";
|
|
4
|
-
|
|
5
|
-
export type StarknetGas = {l1?: number, l2?: number};
|
|
6
|
-
|
|
7
|
-
export function sumStarknetGas(a: StarknetGas, b: StarknetGas) {
|
|
8
|
-
return {
|
|
9
|
-
l1: (a?.l1 ?? 0) + (b?.l1 ?? 0),
|
|
10
|
-
l2: (a?.l2 ?? 0) + (b?.l2 ?? 0)
|
|
11
|
-
}
|
|
12
|
-
}
|
|
4
|
+
import {StarknetGas, starknetGasAdd} from "./modules/StarknetFees";
|
|
13
5
|
|
|
14
6
|
export class StarknetAction {
|
|
15
7
|
|
|
16
|
-
|
|
17
|
-
L2GasLimit: number;
|
|
8
|
+
gas: StarknetGas;
|
|
18
9
|
readonly mainSigner: string;
|
|
19
10
|
private readonly root: StarknetChainInterface;
|
|
20
11
|
private readonly instructions: Call[];
|
|
@@ -30,8 +21,11 @@ export class StarknetAction {
|
|
|
30
21
|
this.mainSigner = mainSigner;
|
|
31
22
|
this.root = root;
|
|
32
23
|
this.instructions = Array.isArray(instructions) ? instructions : [instructions];
|
|
33
|
-
this.
|
|
34
|
-
|
|
24
|
+
this.gas = {
|
|
25
|
+
l1Gas: gasLimit?.l1Gas ?? 0,
|
|
26
|
+
l2Gas: gasLimit?.l2Gas ?? 0,
|
|
27
|
+
l1DataGas: gasLimit?.l1DataGas ?? 0,
|
|
28
|
+
};
|
|
35
29
|
this.feeRate = feeRate;
|
|
36
30
|
}
|
|
37
31
|
|
|
@@ -41,8 +35,7 @@ export class StarknetAction {
|
|
|
41
35
|
|
|
42
36
|
public addIx(instruction: Call, gasLimit?: StarknetGas) {
|
|
43
37
|
this.instructions.push(instruction);
|
|
44
|
-
this.
|
|
45
|
-
this.L2GasLimit += gasLimit?.l2 ?? 0;
|
|
38
|
+
this.gas = starknetGasAdd(this.gas, gasLimit);
|
|
46
39
|
}
|
|
47
40
|
|
|
48
41
|
public add(action: StarknetAction): this {
|
|
@@ -51,10 +44,12 @@ export class StarknetAction {
|
|
|
51
44
|
|
|
52
45
|
public addAction(action: StarknetAction, index: number = this.instructions.length): this {
|
|
53
46
|
if(action.mainSigner!==this.mainSigner) throw new Error("Actions need to have the same signer!");
|
|
54
|
-
if(this.
|
|
55
|
-
if(this.
|
|
56
|
-
if(this.
|
|
57
|
-
if(this.
|
|
47
|
+
if(this.gas.l1Gas==null && action.gas.l1Gas!=null) this.gas.l1Gas = action.gas.l1Gas;
|
|
48
|
+
if(this.gas.l2Gas==null && action.gas.l2Gas!=null) this.gas.l2Gas = action.gas.l2Gas;
|
|
49
|
+
if(this.gas.l1DataGas==null && action.gas.l1DataGas!=null) this.gas.l1DataGas = action.gas.l1DataGas;
|
|
50
|
+
if(this.gas.l1Gas!=null && action.gas.l1Gas!=null) this.gas.l1Gas += action.gas.l1Gas;
|
|
51
|
+
if(this.gas.l2Gas!=null && action.gas.l2Gas!=null) this.gas.l2Gas += action.gas.l2Gas;
|
|
52
|
+
if(this.gas.l1DataGas!=null && action.gas.l1DataGas!=null) this.gas.l1DataGas += action.gas.l1DataGas;
|
|
58
53
|
this.instructions.splice(index, 0, ...action.instructions);
|
|
59
54
|
if(this.feeRate==null) this.feeRate = action.feeRate;
|
|
60
55
|
return this;
|
|
@@ -68,7 +63,7 @@ export class StarknetAction {
|
|
|
68
63
|
type: "INVOKE",
|
|
69
64
|
tx: this.instructions,
|
|
70
65
|
details: {
|
|
71
|
-
...this.root.Fees.getFeeDetails(this.
|
|
66
|
+
...this.root.Fees.getFeeDetails(this.gas, feeRate),
|
|
72
67
|
walletAddress: this.mainSigner,
|
|
73
68
|
cairoVersion: "1",
|
|
74
69
|
chainId: this.root.starknetChainId,
|
|
@@ -6,7 +6,7 @@ import {DeployAccountContractPayload} from "starknet";
|
|
|
6
6
|
export class StarknetAccounts extends StarknetModule {
|
|
7
7
|
|
|
8
8
|
public async getAccountDeployTransaction(deploymentData: DeployAccountContractPayload): Promise<StarknetTx> {
|
|
9
|
-
const feeDetails = this.root.Fees.getFeeDetails(
|
|
9
|
+
const feeDetails = this.root.Fees.getFeeDetails({l1DataGas: 500, l2Gas: 5_000*40_000, l1Gas: 0}, await this.root.Fees.getFeeRate());
|
|
10
10
|
const details = {
|
|
11
11
|
...feeDetails,
|
|
12
12
|
walletAddress: deploymentData.contractAddress,
|
|
@@ -4,6 +4,26 @@ import {StarknetTokens} from "./StarknetTokens";
|
|
|
4
4
|
|
|
5
5
|
const MAX_FEE_AGE = 5000;
|
|
6
6
|
|
|
7
|
+
export type StarknetFeeRate = {
|
|
8
|
+
l1GasCost: bigint;
|
|
9
|
+
l2GasCost: bigint;
|
|
10
|
+
l1DataGasCost: bigint;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type StarknetGas = {
|
|
14
|
+
l1Gas: number,
|
|
15
|
+
l2Gas: number,
|
|
16
|
+
l1DataGas: number
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function starknetGasMul(gas: StarknetGas, scalar: number): StarknetGas {
|
|
20
|
+
return {l1Gas: gas.l1Gas * scalar, l2Gas: gas.l2Gas * scalar, l1DataGas: gas.l1DataGas * scalar};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function starknetGasAdd(a: StarknetGas, b: StarknetGas): StarknetGas {
|
|
24
|
+
return {l1Gas: a.l1Gas + b.l1Gas, l2Gas: a.l2Gas + b.l2Gas, l1DataGas: a.l1DataGas + b.l1DataGas};
|
|
25
|
+
}
|
|
26
|
+
|
|
7
27
|
export class StarknetFees {
|
|
8
28
|
|
|
9
29
|
private readonly logger = getLogger("StarknetFees: ");
|
|
@@ -11,25 +31,22 @@ export class StarknetFees {
|
|
|
11
31
|
private readonly feeDA: "L1" | "L2";
|
|
12
32
|
private readonly nonceDA: "L1" | "L2";
|
|
13
33
|
private readonly provider: Provider;
|
|
14
|
-
private readonly
|
|
15
|
-
private readonly maxFeeRate: bigint;
|
|
34
|
+
private readonly maxFeeRate: StarknetFeeRate;
|
|
16
35
|
private readonly feeMultiplierPPM: bigint;
|
|
17
36
|
|
|
18
37
|
private blockFeeCache: {
|
|
19
38
|
timestamp: number,
|
|
20
|
-
feeRate: Promise<
|
|
39
|
+
feeRate: Promise<StarknetFeeRate>
|
|
21
40
|
} = null;
|
|
22
41
|
|
|
23
42
|
constructor(
|
|
24
43
|
provider: Provider,
|
|
25
|
-
|
|
26
|
-
maxFeeRate: number = gasToken==="ETH" ? 100_000_000_000 /*100 GWei*/ : 1_000_000_000_000_000 /*100 * 10000 GWei*/,
|
|
44
|
+
maxFeeRate: StarknetFeeRate = {l1GasCost: 1_000_000_000_000_000n, l2GasCost: 1_000_000_000_000_000n, l1DataGasCost: 1_000_000_000_000_000n} /*100 * 10000 GWei*/,
|
|
27
45
|
feeMultiplier: number = 1.25,
|
|
28
46
|
da?: {fee?: "L1" | "L2", nonce?: "L1" | "L2"}
|
|
29
47
|
) {
|
|
30
48
|
this.provider = provider;
|
|
31
|
-
this.
|
|
32
|
-
this.maxFeeRate = BigInt(maxFeeRate);
|
|
49
|
+
this.maxFeeRate = maxFeeRate;
|
|
33
50
|
this.feeDA = da?.fee ?? "L1";
|
|
34
51
|
this.nonceDA = da?.nonce ?? "L1";
|
|
35
52
|
this.feeMultiplierPPM = BigInt(Math.floor(feeMultiplier*1000000));
|
|
@@ -39,16 +56,20 @@ export class StarknetFees {
|
|
|
39
56
|
* Gets starknet fee rate
|
|
40
57
|
*
|
|
41
58
|
* @private
|
|
42
|
-
* @returns {Promise<
|
|
59
|
+
* @returns {Promise<StarknetFeeRate>} L1 gas price denominated in Wei
|
|
43
60
|
*/
|
|
44
|
-
private async _getFeeRate(): Promise<
|
|
45
|
-
const block = await this.provider.
|
|
46
|
-
|
|
47
|
-
l1GasCost =
|
|
61
|
+
private async _getFeeRate(): Promise<StarknetFeeRate> {
|
|
62
|
+
const block = await this.provider.getBlock("latest");
|
|
63
|
+
|
|
64
|
+
let l1GasCost = toBigInt(block.l1_gas_price.price_in_fri) * this.feeMultiplierPPM / 1000000n;
|
|
65
|
+
let l1DataGasCost = toBigInt(block.l1_data_gas_price.price_in_fri) * this.feeMultiplierPPM / 1000000n;
|
|
66
|
+
let l2GasCost = toBigInt(block.l2_gas_price.price_in_fri) * this.feeMultiplierPPM / 1000000n;
|
|
48
67
|
|
|
49
|
-
this.logger.debug("_getFeeRate(): L1 fee rate: "
|
|
68
|
+
this.logger.debug("_getFeeRate(): L1 fee rate: ",[l1GasCost.toString(10), l1DataGasCost.toString(10), l2GasCost.toString(10)]);
|
|
50
69
|
|
|
51
|
-
return
|
|
70
|
+
return {
|
|
71
|
+
l1GasCost, l2GasCost, l1DataGasCost
|
|
72
|
+
};
|
|
52
73
|
}
|
|
53
74
|
|
|
54
75
|
/**
|
|
@@ -69,10 +90,12 @@ export class StarknetFees {
|
|
|
69
90
|
this.blockFeeCache = obj;
|
|
70
91
|
}
|
|
71
92
|
|
|
72
|
-
let
|
|
73
|
-
if(
|
|
93
|
+
let {l1GasCost, l2GasCost, l1DataGasCost} = await this.blockFeeCache.feeRate;
|
|
94
|
+
if(l1GasCost>this.maxFeeRate.l1GasCost) l1GasCost = this.maxFeeRate.l1GasCost;
|
|
95
|
+
if(l2GasCost>this.maxFeeRate.l2GasCost) l2GasCost = this.maxFeeRate.l2GasCost;
|
|
96
|
+
if(l1DataGasCost>this.maxFeeRate.l1DataGasCost) l1DataGasCost = this.maxFeeRate.l1DataGasCost;
|
|
74
97
|
|
|
75
|
-
const fee =
|
|
98
|
+
const fee = l1GasCost.toString(10)+","+l2GasCost.toString(10)+","+l1DataGasCost.toString(10)+";v3";
|
|
76
99
|
|
|
77
100
|
this.logger.debug("getFeeRate(): calculated fee: "+fee);
|
|
78
101
|
|
|
@@ -80,22 +103,24 @@ export class StarknetFees {
|
|
|
80
103
|
}
|
|
81
104
|
|
|
82
105
|
public getDefaultGasToken(): string {
|
|
83
|
-
return
|
|
106
|
+
return StarknetTokens.ERC20_STRK;
|
|
84
107
|
}
|
|
85
108
|
|
|
86
109
|
/**
|
|
87
|
-
* Calculates the total gas fee
|
|
110
|
+
* Calculates the total gas fee paid for a given gas limit at a given fee rate
|
|
88
111
|
*
|
|
89
112
|
* @param gas
|
|
90
113
|
* @param feeRate
|
|
91
114
|
*/
|
|
92
|
-
public static getGasFee(gas: number, feeRate: string): bigint {
|
|
115
|
+
public static getGasFee(gas: {l1DataGas: number, l2Gas: number, l1Gas: number}, feeRate: string): bigint {
|
|
93
116
|
if(feeRate==null) return 0n;
|
|
94
117
|
|
|
95
118
|
const arr = feeRate.split(";");
|
|
96
|
-
const
|
|
119
|
+
const [l1GasCostStr, l2GasCostStr, l1DataGasCostStr] = arr[0].split(",");
|
|
97
120
|
|
|
98
|
-
return
|
|
121
|
+
return (BigInt(gas.l1Gas) * BigInt(l1GasCostStr)) +
|
|
122
|
+
(BigInt(gas.l2Gas) * BigInt(l2GasCostStr)) +
|
|
123
|
+
(BigInt(gas.l1DataGas) * BigInt(l1DataGasCostStr));
|
|
99
124
|
}
|
|
100
125
|
|
|
101
126
|
public static getGasToken(feeRate: string): string {
|
|
@@ -107,21 +132,18 @@ export class StarknetFees {
|
|
|
107
132
|
return txVersion==="v1" ? StarknetTokens.ERC20_ETH : StarknetTokens.ERC20_STRK;
|
|
108
133
|
}
|
|
109
134
|
|
|
110
|
-
getFeeDetails(
|
|
135
|
+
getFeeDetails(gas: {l1DataGas: number, l2Gas: number, l1Gas: number}, feeRate: string) {
|
|
111
136
|
if(feeRate==null) return null;
|
|
112
137
|
|
|
113
138
|
const arr = feeRate.split(";");
|
|
114
|
-
const
|
|
115
|
-
const version = arr[1] as "v1" | "v3";
|
|
116
|
-
|
|
117
|
-
const maxFee = toHex(BigInt(L1GasLimit) * gasPrice, 16);
|
|
139
|
+
const [l1GasCostStr, l2GasCostStr, l1DataGasCostStr] = arr[0].split(",");
|
|
118
140
|
|
|
119
141
|
return {
|
|
120
|
-
|
|
121
|
-
version: version==="v1" ? "0x1" : "0x3" as "0x1" | "0x3",
|
|
142
|
+
version: "0x3" as const,
|
|
122
143
|
resourceBounds: {
|
|
123
|
-
l1_gas: {max_amount: toHex(
|
|
124
|
-
l2_gas: {max_amount:
|
|
144
|
+
l1_gas: {max_amount: toHex(gas.l1Gas, 16), max_price_per_unit: toHex(toBigInt(l1GasCostStr), 16)},
|
|
145
|
+
l2_gas: {max_amount: toHex(gas.l2Gas, 16), max_price_per_unit: toHex(toBigInt(l2GasCostStr), 16)},
|
|
146
|
+
l1_data_gas: {max_amount: toHex(gas.l1DataGas, 16), max_price_per_unit: toHex(toBigInt(l1DataGasCostStr), 16)}
|
|
125
147
|
},
|
|
126
148
|
tip: "0x0",
|
|
127
149
|
paymasterData: [],
|
|
@@ -54,11 +54,12 @@ export class StarknetSignatures extends StarknetModule {
|
|
|
54
54
|
return JSON.stringify(stark.formatSignature(signature));
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
public async isValidSignature(signature: string, address: string, type: StarknetType[], typeName: string, message: object) {
|
|
58
|
-
return
|
|
57
|
+
public async isValidSignature(signature: string, address: string, type: StarknetType[], typeName: string, message: object): Promise<boolean> {
|
|
58
|
+
return this.provider.verifyMessageInStarknet(
|
|
59
59
|
this.getTypedMessage(type, typeName, message),
|
|
60
|
-
JSON.parse(signature)
|
|
61
|
-
|
|
60
|
+
JSON.parse(signature),
|
|
61
|
+
address
|
|
62
|
+
);
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
///////////////////
|
|
@@ -12,8 +12,8 @@ export class StarknetTokens extends StarknetModule {
|
|
|
12
12
|
public static readonly ERC20_STRK = "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
|
|
13
13
|
|
|
14
14
|
public static readonly GasCosts = {
|
|
15
|
-
TRANSFER: {
|
|
16
|
-
APPROVE: {
|
|
15
|
+
TRANSFER: {l1DataGas: 400, l2Gas: 4_000_000, l1Gas: 0},
|
|
16
|
+
APPROVE: {l1DataGas: 400, l2Gas: 4_000_000, l1Gas: 0}
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
private getContract(address: string) {
|
|
@@ -51,10 +51,10 @@ export class StarknetSpvVaultContract
|
|
|
51
51
|
>
|
|
52
52
|
{
|
|
53
53
|
private static readonly GasCosts = {
|
|
54
|
-
DEPOSIT: {
|
|
55
|
-
OPEN: {
|
|
56
|
-
FRONT: {
|
|
57
|
-
CLAIM: {
|
|
54
|
+
DEPOSIT: {l1DataGas: 400, l2Gas: 4_000_000, l1Gas: 0},
|
|
55
|
+
OPEN: {l1DataGas: 1200, l2Gas: 3_200_000, l1Gas: 0},
|
|
56
|
+
FRONT: {l1DataGas: 800, l2Gas: 12_000_000, l1Gas: 0},
|
|
57
|
+
CLAIM: {l1DataGas: 1000, l2Gas: 400_000_000, l1Gas: 0}
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
readonly chainId = "STARKNET";
|
|
@@ -466,12 +466,12 @@ export class StarknetSpvVaultContract
|
|
|
466
466
|
|
|
467
467
|
async getClaimFee(signer: string, withdrawalData: StarknetSpvWithdrawalData, feeRate?: string): Promise<bigint> {
|
|
468
468
|
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
469
|
-
return StarknetFees.getGasFee(StarknetSpvVaultContract.GasCosts.CLAIM
|
|
469
|
+
return StarknetFees.getGasFee(StarknetSpvVaultContract.GasCosts.CLAIM, feeRate);
|
|
470
470
|
}
|
|
471
471
|
|
|
472
472
|
async getFrontFee(signer: string, withdrawalData: StarknetSpvWithdrawalData, feeRate?: string): Promise<bigint> {
|
|
473
473
|
feeRate ??= await this.Chain.Fees.getFeeRate();
|
|
474
|
-
return StarknetFees.getGasFee(StarknetSpvVaultContract.GasCosts.FRONT
|
|
474
|
+
return StarknetFees.getGasFee(StarknetSpvVaultContract.GasCosts.FRONT, feeRate);
|
|
475
475
|
}
|
|
476
476
|
|
|
477
477
|
}
|