bridge_ui: eth wrapped assets and getSignedVAA

Change-Id: I1beaeefb7863c0543e180ed2e15e91c645b89299
This commit is contained in:
Evan Gray 2021-08-04 12:08:46 -04:00 committed by Hendrik Hofstadt
parent 3aecf65f4d
commit 5187120fa0
14 changed files with 269 additions and 108 deletions

View File

@ -9,6 +9,7 @@
"version": "0.1.0", "version": "0.1.0",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@improbable-eng/grpc-web": "^0.13.0",
"@material-ui/core": "^4.12.2", "@material-ui/core": "^4.12.2",
"@metamask/detect-provider": "^1.2.0", "@metamask/detect-provider": "^1.2.0",
"@project-serum/sol-wallet-adapter": "^0.2.5", "@project-serum/sol-wallet-adapter": "^0.2.5",
@ -4011,8 +4012,6 @@
"version": "0.13.0", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.13.0.tgz",
"integrity": "sha512-vaxxT+Qwb7GPqDQrBV4vAAfH0HywgOLw6xGIKXd9Q8hcV63CQhmS3p4+pZ9/wVvt4Ph3ZDK9fdC983b9aGMUFg==", "integrity": "sha512-vaxxT+Qwb7GPqDQrBV4vAAfH0HywgOLw6xGIKXd9Q8hcV63CQhmS3p4+pZ9/wVvt4Ph3ZDK9fdC983b9aGMUFg==",
"dev": true,
"optional": true,
"dependencies": { "dependencies": {
"browser-headers": "^0.4.0" "browser-headers": "^0.4.0"
}, },
@ -10309,9 +10308,7 @@
"node_modules/browser-headers": { "node_modules/browser-headers": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/browser-headers/-/browser-headers-0.4.1.tgz", "resolved": "https://registry.npmjs.org/browser-headers/-/browser-headers-0.4.1.tgz",
"integrity": "sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg==", "integrity": "sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg=="
"dev": true,
"optional": true
}, },
"node_modules/browser-process-hrtime": { "node_modules/browser-process-hrtime": {
"version": "1.0.0", "version": "1.0.0",
@ -17417,9 +17414,7 @@
"node_modules/google-protobuf": { "node_modules/google-protobuf": {
"version": "3.17.3", "version": "3.17.3",
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.17.3.tgz", "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.17.3.tgz",
"integrity": "sha512-OVPzcSWIAJ+d5yiHyeaLrdufQtrvaBrF4JQg+z8ynTkbO3uFcujqXszTumqg1cGsAsjkWnI+M5B1xZ19yR4Wyg==", "integrity": "sha512-OVPzcSWIAJ+d5yiHyeaLrdufQtrvaBrF4JQg+z8ynTkbO3uFcujqXszTumqg1cGsAsjkWnI+M5B1xZ19yR4Wyg=="
"dev": true,
"optional": true
}, },
"node_modules/got": { "node_modules/got": {
"version": "9.6.0", "version": "9.6.0",
@ -41493,8 +41488,6 @@
"version": "0.13.0", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.13.0.tgz",
"integrity": "sha512-vaxxT+Qwb7GPqDQrBV4vAAfH0HywgOLw6xGIKXd9Q8hcV63CQhmS3p4+pZ9/wVvt4Ph3ZDK9fdC983b9aGMUFg==", "integrity": "sha512-vaxxT+Qwb7GPqDQrBV4vAAfH0HywgOLw6xGIKXd9Q8hcV63CQhmS3p4+pZ9/wVvt4Ph3ZDK9fdC983b9aGMUFg==",
"dev": true,
"optional": true,
"requires": { "requires": {
"browser-headers": "^0.4.0" "browser-headers": "^0.4.0"
} }
@ -46695,9 +46688,7 @@
"browser-headers": { "browser-headers": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/browser-headers/-/browser-headers-0.4.1.tgz", "resolved": "https://registry.npmjs.org/browser-headers/-/browser-headers-0.4.1.tgz",
"integrity": "sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg==", "integrity": "sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg=="
"dev": true,
"optional": true
}, },
"browser-process-hrtime": { "browser-process-hrtime": {
"version": "1.0.0", "version": "1.0.0",
@ -52508,9 +52499,7 @@
"google-protobuf": { "google-protobuf": {
"version": "3.17.3", "version": "3.17.3",
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.17.3.tgz", "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.17.3.tgz",
"integrity": "sha512-OVPzcSWIAJ+d5yiHyeaLrdufQtrvaBrF4JQg+z8ynTkbO3uFcujqXszTumqg1cGsAsjkWnI+M5B1xZ19yR4Wyg==", "integrity": "sha512-OVPzcSWIAJ+d5yiHyeaLrdufQtrvaBrF4JQg+z8ynTkbO3uFcujqXszTumqg1cGsAsjkWnI+M5B1xZ19yR4Wyg=="
"dev": true,
"optional": true
}, },
"got": { "got": {
"version": "9.6.0", "version": "9.6.0",

View File

@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@improbable-eng/grpc-web": "^0.13.0",
"@material-ui/core": "^4.12.2", "@material-ui/core": "^4.12.2",
"@metamask/detect-provider": "^1.2.0", "@metamask/detect-provider": "^1.2.0",
"@project-serum/sol-wallet-adapter": "^0.2.5", "@project-serum/sol-wallet-adapter": "^0.2.5",

View File

@ -1,5 +1,6 @@
import { import {
Button, Button,
CircularProgress,
Grid, Grid,
makeStyles, makeStyles,
MenuItem, MenuItem,
@ -11,6 +12,7 @@ import { useEthereumProvider } from "../contexts/EthereumProviderContext";
import { useSolanaWallet } from "../contexts/SolanaWalletContext"; import { useSolanaWallet } from "../contexts/SolanaWalletContext";
import useEthereumBalance from "../hooks/useEthereumBalance"; import useEthereumBalance from "../hooks/useEthereumBalance";
import useSolanaBalance from "../hooks/useSolanaBalance"; import useSolanaBalance from "../hooks/useSolanaBalance";
import useWrappedAsset from "../hooks/useWrappedAsset";
import { import {
ChainId, ChainId,
CHAINS, CHAINS,
@ -105,8 +107,18 @@ function Transfer() {
decimals: solDecimals, decimals: solDecimals,
uiAmount: solBalance, uiAmount: solBalance,
} = useSolanaBalance(assetAddress, solPK, fromChain === CHAIN_ID_SOLANA); } = useSolanaBalance(assetAddress, solPK, fromChain === CHAIN_ID_SOLANA);
const { isLoading: isCheckingWrapped, wrappedAsset } = useWrappedAsset(
toChain,
fromChain,
assetAddress,
provider
);
console.log(isCheckingWrapped, wrappedAsset);
// TODO: make a helper function for this
const isWrapped = true;
//wrappedAsset && wrappedAsset !== ethers.constants.AddressZero;
// TODO: dynamically get "to" wallet // TODO: dynamically get "to" wallet
const handleClick = useCallback(() => { const handleTransferClick = useCallback(() => {
// TODO: more generic way of calling these // TODO: more generic way of calling these
if (transferFrom[fromChain]) { if (transferFrom[fromChain]) {
if ( if (
@ -210,39 +222,77 @@ function Transfer() {
value={assetAddress} value={assetAddress}
onChange={handleAssetChange} onChange={handleAssetChange}
/> />
<TextField {isWrapped ? (
placeholder="Amount" <>
type="number" <TextField
fullWidth placeholder="Amount"
className={classes.transferField} type="number"
value={amount} fullWidth
onChange={handleAmountChange} className={classes.transferField}
/> value={amount}
<Button onChange={handleAmountChange}
color="primary" />
variant="contained" <Button
className={classes.transferButton} color="primary"
onClick={handleClick} variant="contained"
disabled={!canAttemptTransfer} className={classes.transferButton}
> onClick={handleTransferClick}
Transfer disabled={!canAttemptTransfer}
</Button> >
{canAttemptTransfer ? null : ( Transfer
<Typography variant="body2" color="error"> </Button>
{!isTransferImplemented {canAttemptTransfer ? null : (
? `Transfer is not yet implemented for ${CHAINS_BY_ID[fromChain].name}` <Typography variant="body2" color="error">
: !isProviderConnected {!isTransferImplemented
? "The source wallet is not connected" ? `Transfer is not yet implemented for ${CHAINS_BY_ID[fromChain].name}`
: !isRecipientAvailable : !isProviderConnected
? "The receiving wallet is not connected" ? "The source wallet is not connected"
: !isAddressDefined : !isRecipientAvailable
? "Please provide an asset address" ? "The receiving wallet is not connected"
: !isAmountPositive : !isAddressDefined
? "The amount must be positive" ? "Please provide an asset address"
: !isBalanceAtLeastAmount : !isAmountPositive
? "The amount may not be greater than the balance" ? "The amount must be positive"
: ""} : !isBalanceAtLeastAmount
</Typography> ? "The amount may not be greater than the balance"
: ""}
</Typography>
)}
</>
) : (
<>
<div style={{ position: "relative" }}>
<Button
color="primary"
variant="contained"
disabled={isCheckingWrapped}
className={classes.transferButton}
>
Attest
</Button>
{isCheckingWrapped ? (
<CircularProgress
size={24}
color="inherit"
style={{
position: "absolute",
bottom: 0,
left: "50%",
marginLeft: -12,
marginBottom: 6,
}}
/>
) : null}
</div>
{isCheckingWrapped ? null : (
<Typography variant="body2">
<br />
This token does not exist on {CHAINS_BY_ID[toChain].name}. Someone
must attest the the token to the target chain before it can be
transferred.
</Typography>
)}
</>
)} )}
</div> </div>
); );

View File

@ -20,12 +20,10 @@ function useEthereumBalance(
token token
.decimals() .decimals()
.then((decimals) => { .then((decimals) => {
console.log(decimals);
provider provider
?.getSigner() ?.getSigner()
.getAddress() .getAddress()
.then((pk) => { .then((pk) => {
console.log(pk);
token.balanceOf(pk).then((n) => { token.balanceOf(pk).then((n) => {
if (!cancelled) { if (!cancelled) {
setBalance(formatUnits(n, decimals)); setBalance(formatUnits(n, decimals));

View File

@ -0,0 +1,41 @@
import { ethers } from "ethers";
import { useEffect, useState } from "react";
import { ChainId, CHAIN_ID_ETH } from "../utils/consts";
import { wrappedAssetEth } from "../utils/wrappedAsset";
export interface WrappedAssetState {
isLoading: boolean;
wrappedAsset: string | null;
}
function useWrappedAsset(
checkChain: ChainId,
originChain: ChainId,
originAsset: string,
provider: ethers.providers.Web3Provider | undefined
) {
const [state, setState] = useState<WrappedAssetState>({
isLoading: false,
wrappedAsset: null,
});
useEffect(() => {
let cancelled = false;
(async () => {
if (provider && checkChain === CHAIN_ID_ETH) {
setState({ isLoading: true, wrappedAsset: null });
const asset = await wrappedAssetEth(provider, originChain, originAsset);
if (!cancelled) {
setState({ isLoading: false, wrappedAsset: asset });
}
} else {
setState({ isLoading: false, wrappedAsset: null });
}
})();
return () => {
cancelled = true;
};
}, [checkChain, originChain, originAsset, provider]);
return state;
}
export default useWrappedAsset;

View File

@ -8,7 +8,7 @@ export const theme = responsiveFontSizes(
default: "#010114", default: "#010114",
paper: "#010114", paper: "#010114",
}, },
divider: "#FFFFFF", divider: "#4e4e54",
primary: { primary: {
main: "#0074FF", main: "#0074FF",
}, },

View File

@ -0,0 +1,34 @@
import {
GrpcWebImpl,
PublicrpcClientImpl,
} from "../proto/publicrpc/v1/publicrpc";
import { ChainId } from "../utils/consts";
export async function getSignedVAA(
emitterChain: ChainId,
emitterAddress: string,
sequence: string
) {
const rpc = new GrpcWebImpl("http://localhost:8080", {});
const api = new PublicrpcClientImpl(rpc);
// TODO: potential infinite loop, support cancellation?
let result;
while (!result) {
console.log("wait 1 second");
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log("check for signed vaa", emitterChain, emitterAddress, sequence);
try {
result = await api.GetSignedVAA({
messageId: {
emitterChain,
emitterAddress,
sequence,
},
});
console.log(result);
} catch (e) {
console.log(e);
}
}
return result;
}

View File

@ -1,3 +1,5 @@
import { getAddress } from "ethers/lib/utils";
export type ChainId = 1 | 2 | 3 | 4; export type ChainId = 1 | 2 | 3 | 4;
export const CHAIN_ID_SOLANA: ChainId = 1; export const CHAIN_ID_SOLANA: ChainId = 1;
export const CHAIN_ID_ETH: ChainId = 2; export const CHAIN_ID_ETH: ChainId = 2;
@ -31,10 +33,15 @@ export const CHAINS_BY_ID: ChainsById = CHAINS.reduce((obj, chain) => {
return obj; return obj;
}, {} as ChainsById); }, {} as ChainsById);
export const SOLANA_HOST = "http://localhost:8899"; export const SOLANA_HOST = "http://localhost:8899";
export const ETH_TEST_TOKEN_ADDRESS = export const ETH_TEST_TOKEN_ADDRESS = getAddress(
"0x0290FB167208Af455bB137780163b7B7a9a10C16"; "0x0290FB167208Af455bB137780163b7B7a9a10C16"
export const ETH_TOKEN_BRIDGE_ADDRESS = );
"0xe982e462b094850f12af94d21d470e21be9d0e9c"; export const ETH_BRIDGE_ADDRESS = getAddress(
"0x254dffcd3277c0b1660f6d42efbb754edababc2b"
);
export const ETH_TOKEN_BRIDGE_ADDRESS = getAddress(
"0xe982e462b094850f12af94d21d470e21be9d0e9c"
);
export const SOL_TEST_TOKEN_ADDRESS = export const SOL_TEST_TOKEN_ADDRESS =
"2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ"; "2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ";
export const SOL_BRIDGE_ADDRESS = "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"; export const SOL_BRIDGE_ADDRESS = "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o";

View File

@ -1,4 +1,5 @@
import Wallet from "@project-serum/sol-wallet-adapter"; import Wallet from "@project-serum/sol-wallet-adapter";
import { Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { import {
AccountMeta, AccountMeta,
Connection, Connection,
@ -7,17 +8,19 @@ import {
Transaction, Transaction,
TransactionInstruction, TransactionInstruction,
} from "@solana/web3.js"; } from "@solana/web3.js";
import { Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { ethers } from "ethers"; import { ethers } from "ethers";
import { arrayify, formatUnits, parseUnits, zeroPad } from "ethers/lib/utils"; import { arrayify, formatUnits, parseUnits, zeroPad } from "ethers/lib/utils";
import { import {
Bridge__factory, Bridge__factory,
Implementation__factory,
TokenImplementation__factory, TokenImplementation__factory,
} from "../ethers-contracts"; } from "../ethers-contracts";
import { getSignedVAA } from "../sdk";
import { import {
ChainId, ChainId,
CHAIN_ID_ETH, CHAIN_ID_ETH,
CHAIN_ID_SOLANA, CHAIN_ID_SOLANA,
ETH_BRIDGE_ADDRESS,
ETH_TOKEN_BRIDGE_ADDRESS, ETH_TOKEN_BRIDGE_ADDRESS,
SOLANA_HOST, SOLANA_HOST,
SOL_BRIDGE_ADDRESS, SOL_BRIDGE_ADDRESS,
@ -41,46 +44,60 @@ export function transferFromEth(
//TODO: don't hardcode, fetch decimals / share them with balance, how do we determine recipient chain? //TODO: don't hardcode, fetch decimals / share them with balance, how do we determine recipient chain?
//TODO: more catches //TODO: more catches
const amountParsed = parseUnits(amount, 18); const amountParsed = parseUnits(amount, 18);
signer.getAddress().then((signerAddress) => { (async () => {
const signerAddress = await signer.getAddress();
console.log("Signer:", signerAddress); console.log("Signer:", signerAddress);
console.log("Token:", tokenAddress); console.log("Token:", tokenAddress);
const token = TokenImplementation__factory.connect(tokenAddress, signer); const token = TokenImplementation__factory.connect(tokenAddress, signer);
token const allowance = await token.allowance(
.allowance(signerAddress, ETH_TOKEN_BRIDGE_ADDRESS) signerAddress,
.then((allowance) => { ETH_TOKEN_BRIDGE_ADDRESS
console.log("Allowance", allowance.toString()); //TODO: should we check that this is zero and warn if it isn't? );
token console.log("Allowance", allowance.toString()); //TODO: should we check that this is zero and warn if it isn't?
.approve(ETH_TOKEN_BRIDGE_ADDRESS, amountParsed) const transaction = await token.approve(
.then((transaction) => { ETH_TOKEN_BRIDGE_ADDRESS,
console.log(transaction); amountParsed
const fee = 0; // for now, this won't do anything, we may add later );
const nonceConst = Math.random() * 100000; console.log(transaction);
const nonceBuffer = Buffer.alloc(4); const fee = 0; // for now, this won't do anything, we may add later
nonceBuffer.writeUInt32LE(nonceConst, 0); const nonceConst = Math.random() * 100000;
console.log("Initiating transfer"); const nonceBuffer = Buffer.alloc(4);
console.log("Amount:", formatUnits(amountParsed, 18)); nonceBuffer.writeUInt32LE(nonceConst, 0);
console.log("To chain:", recipientChain); console.log("Initiating transfer");
console.log("To address:", recipientAddress); console.log("Amount:", formatUnits(amountParsed, 18));
console.log("Fees:", fee); console.log("To chain:", recipientChain);
console.log("Nonce:", nonceBuffer); console.log("To address:", recipientAddress);
const bridge = Bridge__factory.connect( console.log("Fees:", fee);
ETH_TOKEN_BRIDGE_ADDRESS, console.log("Nonce:", nonceBuffer);
signer const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer);
); const v = await bridge.transferTokens(
bridge tokenAddress,
.transferTokens( amountParsed,
tokenAddress, recipientChain,
amountParsed, recipientAddress,
recipientChain, fee,
recipientAddress, nonceBuffer
fee, );
nonceBuffer const receipt = await v.wait();
) // TODO: dangerous!(?)
.then((v) => console.log("Success:", v)) const bridgeLog = receipt.logs.filter((l) => {
.catch((r) => console.error(r)); //TODO: integrate toast messages console.log(l.address, ETH_BRIDGE_ADDRESS);
}); return l.address === ETH_BRIDGE_ADDRESS;
}); })[0];
}); const {
args: { sender, sequence },
} = Implementation__factory.createInterface().parseLog(bridgeLog);
console.log(sender, sequence);
const emitterAddress = Buffer.from(
zeroPad(arrayify(ETH_TOKEN_BRIDGE_ADDRESS), 32)
).toString("hex");
const { vaaBytes } = await getSignedVAA(
CHAIN_ID_ETH,
emitterAddress,
sequence
);
console.log("SIGNED VAA:", vaaBytes);
})();
} }
// TODO: should we share this with client? ooh, should client use the SDK ;) // TODO: should we share this with client? ooh, should client use the SDK ;)
@ -201,8 +218,8 @@ export function transferFromSolana(
console.log("SIGNED", signed); console.log("SIGNED", signed);
const txid = await connection.sendRawTransaction(signed.serialize()); const txid = await connection.sendRawTransaction(signed.serialize());
console.log("SENT", txid); console.log("SENT", txid);
await connection.confirmTransaction(txid); const conf = await connection.confirmTransaction(txid);
console.log("CONFIRMED"); console.log("CONFIRMED", conf);
})(); })();
} }

View File

@ -0,0 +1,24 @@
import { PublicKey } from "@solana/web3.js";
import { ethers } from "ethers";
import { arrayify, zeroPad } from "ethers/lib/utils";
import { Bridge__factory } from "../ethers-contracts";
import { ChainId, CHAIN_ID_SOLANA, ETH_TOKEN_BRIDGE_ADDRESS } from "./consts";
export function wrappedAssetEth(
provider: ethers.providers.Web3Provider,
originChain: ChainId,
originAsset: string
) {
const tokenBridge = Bridge__factory.connect(
ETH_TOKEN_BRIDGE_ADDRESS,
provider
);
// TODO: address conversion may be more complex than this
const originAssetBytes = zeroPad(
originChain === CHAIN_ID_SOLANA
? new PublicKey(originAsset).toBytes()
: arrayify(originAsset),
32
);
return tokenBridge.wrappedAsset(originChain, originAssetBytes);
}

View File

@ -17,6 +17,7 @@
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"downlevelIteration": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx" "jsx": "react-jsx"
}, },

View File

@ -19,4 +19,4 @@ plugins:
- env=browser - env=browser
- forceLong=string - forceLong=string
- outputClientImpl=grpc-web - outputClientImpl=grpc-web
- explorer/src/proto - bridge_ui/src/proto

View File

@ -7,7 +7,7 @@
"": { "": {
"version": "1.0.0", "version": "1.0.0",
"devDependencies": { "devDependencies": {
"ts-proto": "^1.81.1" "ts-proto": "^1.82.3"
} }
}, },
"node_modules/@protobufjs/aspromise": { "node_modules/@protobufjs/aspromise": {
@ -175,9 +175,9 @@
} }
}, },
"node_modules/ts-proto": { "node_modules/ts-proto": {
"version": "1.82.0", "version": "1.82.3",
"resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.82.0.tgz", "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.82.3.tgz",
"integrity": "sha512-vo4QN4QhR0D4/+C/pSbRIVSV6U7dooNcuyW3SL9DvhKRQA4lnAbF5QBs77ge3JRi+aSZJm8MlzTNk7+e++fvvQ==", "integrity": "sha512-ODveOXK2imsgTiqkBcJu9mIOklmCTSzs7Xu+mT8Xljwh3Wenhax7bhty+x2eO4J7AfNkikXH0Xs7K3lk3UT8VA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/object-hash": "^1.3.0", "@types/object-hash": "^1.3.0",
@ -354,9 +354,9 @@
} }
}, },
"ts-proto": { "ts-proto": {
"version": "1.82.0", "version": "1.82.3",
"resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.82.0.tgz", "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-1.82.3.tgz",
"integrity": "sha512-vo4QN4QhR0D4/+C/pSbRIVSV6U7dooNcuyW3SL9DvhKRQA4lnAbF5QBs77ge3JRi+aSZJm8MlzTNk7+e++fvvQ==", "integrity": "sha512-ODveOXK2imsgTiqkBcJu9mIOklmCTSzs7Xu+mT8Xljwh3Wenhax7bhty+x2eO4J7AfNkikXH0Xs7K3lk3UT8VA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/object-hash": "^1.3.0", "@types/object-hash": "^1.3.0",

View File

@ -3,7 +3,6 @@
"version": "1.0.0", "version": "1.0.0",
"description": "tooling for building web code from protobufs", "description": "tooling for building web code from protobufs",
"devDependencies": { "devDependencies": {
"ts-proto": "^1.81.1" "ts-proto": "^1.82.3"
}, }
"dependencies": {}
} }