Skip to content

Commit 9909069

Browse files
authored
Merge pull request #31 from syscoin/sponsor-wallet-implementation
Move transaction submission on UI side Fix NEVM Freeze and burn issue
2 parents 0aabbe7 + 0884c69 commit 9909069

File tree

13 files changed

+110
-58
lines changed

13 files changed

+110
-58
lines changed

.github/workflows/aws.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
push:
77
branches:
88
- main
9+
- sponsor-wallet-implementation
910

1011
env:
1112
AWS_REGION: us-east-1 # set this to your preferred AWS region, e.g. us-west-1

api/services/sponsor-wallet.ts

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { DEFAULT_GAS_LIMIT } from "@constants";
12
import SponsorWallet, { ISponsorWallet } from "models/sponsor-wallet";
23
import SponsorWalletTransactions, {
34
ISponsorWalletTransaction,
4-
SponsorWalletTransactionStatus,
5+
SponsorWalletTransactionCollectionName,
56
} from "models/sponsor-wallet-transactions";
67
import web3 from "utils/get-web3";
78
import { TransactionConfig } from "web3-core";
@@ -44,12 +45,20 @@ export class SponsorWalletService {
4445
return existingTransaction;
4546
}
4647

47-
const nonce = await this.getWalletNextNonce(wallet.id);
48+
const nonce = await this.getWalletNextNonce(wallet._id);
4849

4950
const sender = web3.eth.accounts.privateKeyToAccount(wallet.privateKey);
5051

52+
const gasPrice = await web3.eth.getGasPrice();
53+
const gas = await web3.eth.estimateGas(transactionConfig).catch((e) => {
54+
console.error("estimateGas error", e);
55+
return DEFAULT_GAS_LIMIT;
56+
});
57+
5158
const signedTransaction = await sender.signTransaction({
5259
...transactionConfig,
60+
gasPrice: web3.utils.toHex(gasPrice),
61+
gas: web3.utils.toHex(gas),
5362
nonce,
5463
});
5564

@@ -62,27 +71,15 @@ export class SponsorWalletService {
6271

6372
let walletTransaction = new SponsorWalletTransactions({
6473
transferId: transferId,
65-
walletId: wallet.id,
66-
transactionHash: signedTransaction.transactionHash,
74+
walletId: wallet._id,
75+
transaction: {
76+
hash: signedTransaction.transactionHash,
77+
rawData: signedTransaction.rawTransaction,
78+
nonce: nonce,
79+
},
6780
status: "pending",
6881
});
6982

70-
walletTransaction = await walletTransaction.save();
71-
72-
let receipt = await web3.eth
73-
.getTransactionReceipt(signedTransaction.transactionHash)
74-
.catch(() => undefined);
75-
let status: SponsorWalletTransactionStatus = "success";
76-
77-
if (!receipt) {
78-
receipt = await web3.eth.sendSignedTransaction(
79-
signedTransaction.rawTransaction
80-
);
81-
status = "pending";
82-
}
83-
84-
walletTransaction.status = status;
85-
8683
return walletTransaction.save();
8784
}
8885

@@ -101,31 +98,32 @@ export class SponsorWalletService {
10198
}
10299

103100
transaction.status = receipt.status ? "success" : "failed";
101+
transaction.transaction.confirmedHash = receipt.transactionHash;
104102
await transaction.save();
105103
}
106104

107105
private async getWalletNextNonce(walletId: string): Promise<number> {
108-
const wallet = await SponsorWallet.findOne({ id: walletId });
106+
const wallet = await SponsorWallet.findById(walletId);
109107
if (!wallet) {
110108
throw new Error("Wallet not found");
111109
}
112-
const lastTransaction = await SponsorWalletTransactions.findOne({
110+
const [lastTransaction] = await SponsorWalletTransactions.find({
113111
walletId: wallet?.id,
114-
}).sort({ createdAt: -1 });
115-
116-
if (!lastTransaction) {
117-
const nonce = await web3.eth.getTransactionCount(
118-
wallet.address,
119-
"pending"
120-
);
121-
return nonce;
122-
}
112+
}).sort({ "transaction.nonce": -1 }).limit(1);
123113

124-
const transaction = await web3.eth.getTransaction(
125-
lastTransaction.transactionHash
114+
const pendingNonce = await web3.eth.getTransactionCount(
115+
wallet.address,
116+
"pending"
126117
);
127118

128-
return transaction.nonce + 1;
119+
console.log("pendingNonce", pendingNonce);
120+
console.log("lastTransaction nonce", lastTransaction?.transaction.nonce);
121+
122+
const internalNonce = lastTransaction
123+
? lastTransaction.transaction.nonce
124+
: -1;
125+
126+
return pendingNonce > internalNonce ? pendingNonce : internalNonce + 1;
129127
}
130128

131129
private async getAvailableWalletForSigning(): Promise<ISponsorWallet> {
@@ -146,7 +144,7 @@ export class SponsorWalletService {
146144
return SponsorWallet.aggregate([
147145
{
148146
$lookup: {
149-
from: "sponsorwallettransactions",
147+
from: SponsorWalletTransactionCollectionName,
150148
localField: "_id",
151149
foreignField: "walletId",
152150
as: "transactions",
@@ -166,7 +164,7 @@ export class SponsorWalletService {
166164
return SponsorWallet.aggregate([
167165
{
168166
$lookup: {
169-
from: "sponsorwallettransactions",
167+
from: SponsorWalletTransactionCollectionName,
170168
localField: "_id",
171169
foreignField: "walletId",
172170
as: "transactions",

api/services/transfer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class TransferService {
4040
}, [] as Record<string, string>[]);
4141
return TransferModel.find({
4242
$or: queryConstraints,
43-
});
43+
}).sort({ createdAt: -1 }) as unknown as ITransfer[];
4444
}
4545

4646
async getTransfer(id: string): Promise<ITransfer> {

components/Bridge/v3/Steps/ConfirmNEVMTransaction.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { ITransferLog, TransferStatus } from "@contexts/Transfer/types";
22
import { useTransfer } from "../context/TransferContext";
3-
import { useWeb3 } from "../context/Web";
43
import { Alert, CircularProgress, Link, Typography } from "@mui/material";
54
import { useNevmTransaction } from "../hooks/useNevmTransaction";
65
import { useEffect } from "react";
76
import { NEVM_TX_BLOCKCHAIN_URL } from "@constants";
7+
import NEVMStepWrapper from "../NEVMStepWrapepr";
88

99
type Props = {
1010
successStatus: TransferStatus;
@@ -70,4 +70,10 @@ const BridgeV3ConfirmNEVMTransaction: React.FC<Props> = ({
7070
);
7171
};
7272

73-
export default BridgeV3ConfirmNEVMTransaction;
73+
const Wrapped: React.FC<Props> = (props) => (
74+
<NEVMStepWrapper>
75+
<BridgeV3ConfirmNEVMTransaction {...props} />
76+
</NEVMStepWrapper>
77+
);
78+
79+
export default Wrapped;

components/Bridge/v3/Steps/ConfirmUTXOTransaction.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { Alert, Box, CircularProgress, Link } from "@mui/material";
22
import { useTransfer } from "../context/TransferContext";
33
import { useUtxoTransaction } from "components/Bridge/v3/hooks/useUtxoTransaction";
44
import { ITransferLog, TransferStatus } from "@contexts/Transfer/types";
5-
import { useEffect } from "react";
5+
import React, { useEffect } from "react";
66
import { SYSCOIN_TX_BLOCKCHAIN_URL } from "@constants";
7+
import UTXOStepWrapper from "../UTXOStepWrapper";
78

89
type Props = {
910
invalidStateMessage: string;
@@ -76,4 +77,10 @@ const BridgeV3StepConfirmUTXOTransaction: React.FC<Props> = ({
7677
);
7778
};
7879

79-
export default BridgeV3StepConfirmUTXOTransaction;
80+
const Wrapped: React.FC<Props> = (props) => (
81+
<UTXOStepWrapper>
82+
<BridgeV3StepConfirmUTXOTransaction {...props} />
83+
</UTXOStepWrapper>
84+
);
85+
86+
export default Wrapped;

components/Bridge/v3/hooks/useFreezeAndBurn.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useMutation } from "react-query";
44
import { SYSX_ASSET_GUID } from "@contexts/Transfer/constants";
55
import { toWei } from "web3-utils";
66
import { useErc20ManagerContract } from "./useErc20ManagerContract";
7+
import { DEFAULT_GAS_LIMIT } from "@constants";
78

89
export const useFreezeAndBurn = (transfer: ITransfer) => {
910
const erc20ManagerContract = useErc20ManagerContract();
@@ -21,6 +22,7 @@ export const useFreezeAndBurn = (transfer: ITransfer) => {
2122

2223
const gas = await method.estimateGas().catch((error: Error) => {
2324
console.error("Estimate gas error", error);
25+
return DEFAULT_GAS_LIMIT
2426
});
2527

2628
return new Promise<string>((resolve, reject) => {

components/Bridge/v3/hooks/useSyscoinSubmitProofs.tsx

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ITransfer } from "@contexts/Transfer/types";
22
import { useMutation } from "react-query";
33
import { useWeb3 } from "../context/Web";
44
import { ISponsorWalletTransaction } from "models/sponsor-wallet-transactions";
5+
import { rejects } from "assert";
56

67
const useSyscoinSubmitProofs = (
78
transfer: ITransfer,
@@ -11,7 +12,7 @@ const useSyscoinSubmitProofs = (
1112
return useMutation(
1213
["syscoin-submit-proofs", transfer.id],
1314
async () => {
14-
const submitProofsResponse: ISponsorWalletTransaction = await fetch(
15+
const sponsorWalletTransaction: ISponsorWalletTransaction = await fetch(
1516
`/api/transfer/${transfer.id}/signed-submit-proofs-tx`
1617
).then((res) => {
1718
if (res.ok) {
@@ -20,13 +21,43 @@ const useSyscoinSubmitProofs = (
2021
return res.json().then(({ message }) => Promise.reject(message));
2122
});
2223

23-
return submitProofsResponse.transactionHash;
24+
const receipt = await web3.eth.getTransactionReceipt(
25+
sponsorWalletTransaction.transaction.hash,
26+
);
27+
28+
if (receipt) {
29+
return receipt.transactionHash;
30+
}
31+
32+
return new Promise<string>((resolve, reject) => {
33+
const method = web3.eth.sendSignedTransaction(
34+
sponsorWalletTransaction.transaction.rawData
35+
);
36+
method
37+
.once("transactionHash", (hash: string | { success: false }) => {
38+
if (typeof hash === "string") {
39+
return resolve(hash);
40+
}
41+
if (!hash.success) {
42+
reject("Failed to submit proofs. Check browser logs");
43+
console.error("Submission failed", hash);
44+
}
45+
})
46+
.on("error", (error: { message: string }) => {
47+
if (/might still be mined/.test(error.message)) {
48+
resolve("");
49+
} else {
50+
console.error(error);
51+
reject(error);
52+
}
53+
});
54+
});
2455
},
2556
{
26-
onSuccess: (data) => {
57+
onSuccess: (data: string) => {
2758
onSuccess(data);
2859
},
29-
retry: 3
60+
retry: 3,
3061
}
3162
);
3263
};

constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export const MIN_AMOUNT = 0.01;
2+
export const DEFAULT_GAS_LIMIT = 120_000
23

34
export const RELAY_CONTRACT_ADDRESS =
45
"0xD822557aC2F2b77A1988617308e4A29A89Cb95A6";

models/sponsor-wallet-transactions.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,48 @@ import mongoose from "mongoose";
22

33
export type SponsorWalletTransactionStatus = "pending" | "success" | "failed";
44

5+
export const SponsorWalletTransactionCollectionName = 'sponsorwallettransactions'
6+
57
export interface ISponsorWalletTransaction extends mongoose.Document {
6-
transferId: string
8+
transferId: string;
79
walletId: string;
8-
transactionHash: string;
910
status: SponsorWalletTransactionStatus;
1011
createdAt: Date;
1112
updatedAt: Date;
13+
transaction: {
14+
hash: string;
15+
rawData: string;
16+
confirmedHash: string;
17+
nonce: number;
18+
};
1219
}
1320

1421
const SponsorWalletTransactionSchema =
1522
new mongoose.Schema<ISponsorWalletTransaction>(
1623
{
1724
transferId: {
18-
type: String
19-
},
20-
walletId: {
2125
type: String,
2226
},
23-
transactionHash: {
27+
walletId: {
2428
type: String,
2529
},
2630
status: {
2731
type: String,
2832
default: "pending",
2933
},
34+
transaction: {
35+
type: Object,
36+
default: {},
37+
},
3038
},
3139
{ timestamps: true }
3240
);
3341

3442
const generateModel = () =>
3543
mongoose.model("SponsorWalletTransaction", SponsorWalletTransactionSchema);
3644

37-
let model: ReturnType<typeof generateModel> = mongoose.models.SponsorWalletTransaction;
45+
let model: ReturnType<typeof generateModel> =
46+
mongoose.models.SponsorWalletTransaction;
3847

3948
if (!model) {
4049
model = generateModel();

pages/api/transfer/[id]/signed-submit-proofs-tx.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,12 @@ const handler: NextApiHandler = async (
5151

5252
const encoded = method.encodeABI();
5353

54-
const gasPrice = await web3.eth.getGasPrice();
55-
const gas = await method.estimateGas();
56-
const gasLimit = gas ?? 400_000;
57-
5854
const sponsorWalletService = new SponsorWalletService();
5955

6056
const sponsoredTransaction = await sponsorWalletService.sponsorTransaction(
6157
transfer.id,
6258
{
6359
to: RELAY_CONTRACT_ADDRESS,
64-
gasPrice: web3.utils.toHex(gasPrice),
65-
gas: web3.utils.toHex(gasLimit),
6660
data: encoded,
6761
value: 0,
6862
}

0 commit comments

Comments
 (0)