add redeem actions

This commit is contained in:
Andrea Franz 2020-04-01 18:27:06 +02:00
parent 3fbbffca65
commit 5d9e3bcb6c
No known key found for this signature in database
GPG Key ID: 4F0D2F2D9DE7F29D
12 changed files with 394 additions and 34 deletions

View File

@ -16,14 +16,15 @@ export interface ErrLoadingGift {
message: string message: string
} }
export type BucketError = export type BucketErrors =
ErrGiftNotFound; ErrGiftNotFound |
ErrLoadingGift;
const errGiftNotFound = () => ({ const errGiftNotFound = (): ErrGiftNotFound => ({
type: ERROR_GIFT_NOT_FOUND, type: ERROR_GIFT_NOT_FOUND,
}); });
const errLoadingGift = (message: string) => ({ const errLoadingGift = (message: string): ErrLoadingGift => ({
type: ERROR_LOADING_GIFT, type: ERROR_LOADING_GIFT,
message, message,
}); });
@ -32,17 +33,19 @@ export const BUCKET_GIFT_LOADING = "BUCKET_GIFT_LOADING";
export interface BucketGiftLoadingAction { export interface BucketGiftLoadingAction {
type: typeof BUCKET_GIFT_LOADING type: typeof BUCKET_GIFT_LOADING
address: string address: string
recipient: string
} }
export const BUCKET_GIFT_LOADING_ERROR = "BUCKET_GIFT_LOADING_ERROR"; export const BUCKET_GIFT_LOADING_ERROR = "BUCKET_GIFT_LOADING_ERROR";
export interface BucketGiftLoadingErrorAction { export interface BucketGiftLoadingErrorAction {
type: typeof BUCKET_GIFT_LOADING_ERROR type: typeof BUCKET_GIFT_LOADING_ERROR
error: BucketError error: ErrLoadingGift
} }
export const BUCKET_GIFT_LOADED = "BUCKET_GIFT_LOADED"; export const BUCKET_GIFT_LOADED = "BUCKET_GIFT_LOADED";
export interface BucketGiftLoadedAction { export interface BucketGiftLoadedAction {
type: typeof BUCKET_GIFT_LOADED type: typeof BUCKET_GIFT_LOADED
expirationTime: number
recipient: string recipient: string
amount: string amount: string
codeHash: string codeHash: string
@ -51,7 +54,7 @@ export interface BucketGiftLoadedAction {
export const BUCKET_GIFT_NOT_FOUND = "BUCKET_GIFT_NOT_FOUND"; export const BUCKET_GIFT_NOT_FOUND = "BUCKET_GIFT_NOT_FOUND";
export interface BucketGiftNotFoundAction { export interface BucketGiftNotFoundAction {
type: typeof BUCKET_GIFT_NOT_FOUND type: typeof BUCKET_GIFT_NOT_FOUND
error: BucketError error: ErrGiftNotFound
} }
export const BUCKET_TOKEN_LOADING = "BUCKET_TOKEN_LOADING"; export const BUCKET_TOKEN_LOADING = "BUCKET_TOKEN_LOADING";
@ -64,7 +67,7 @@ export const BUCKET_TOKEN_LOADED = "BUCKET_TOKEN_LOADED";
export interface BucketTokenLoadedAction { export interface BucketTokenLoadedAction {
type: typeof BUCKET_TOKEN_LOADED type: typeof BUCKET_TOKEN_LOADED
symbol: string symbol: string
decimal: number decimals: number
} }
export type BucketActions = export type BucketActions =
@ -75,25 +78,27 @@ export type BucketActions =
BucketTokenLoadingAction | BucketTokenLoadingAction |
BucketTokenLoadedAction; BucketTokenLoadedAction;
export const loadingGift = (address: string): BucketLoadingAction => ({ export const loadingGift = (address: string, recipient: string): BucketGiftLoadingAction => ({
type: BUCKET_GIFT_LOADING, type: BUCKET_GIFT_LOADING,
address, address,
recipient,
}); });
export const giftLoaded = (recipient: string, amount: string, codeHash: string): BucketGiftLoadedAction => ({ export const giftLoaded = (expirationTime: number, recipient: string, amount: string, codeHash: string): BucketGiftLoadedAction => ({
type: BUCKET_GIFT_LOADED, type: BUCKET_GIFT_LOADED,
expirationTime,
recipient, recipient,
amount, amount,
codeHash, codeHash,
}); });
export const giftNotFound = (recipient: string, amount: string, codeHash: string): BucketGiftNotFoundAction => ({ export const giftNotFound = (): BucketGiftNotFoundAction => ({
type: BUCKET_GIFT_NOT_FOUND, type: BUCKET_GIFT_NOT_FOUND,
error: errGiftNotFound(), error: errGiftNotFound(),
}); });
export const errorLoadingGift = (errorMessage: string): BucketGiftNotFoundAction => ({ export const errorLoadingGift = (errorMessage: string): BucketGiftLoadingErrorAction => ({
type: BUCKET_GIFT_NOT_FOUND, type: BUCKET_GIFT_LOADING_ERROR,
error: errLoadingGift(errorMessage), error: errLoadingGift(errorMessage),
}); });
@ -108,7 +113,7 @@ export const tokenLoaded = (symbol: string, decimals: number): BucketTokenLoaded
decimals, decimals,
}); });
const newBucketContract = (address: string) => { export const newBucketContract = (address: string) => {
const bucketAbi = GiftBucket.options.jsonInterface; const bucketAbi = GiftBucket.options.jsonInterface;
const bucket = new config.web3!.eth.Contract(bucketAbi, address); const bucket = new config.web3!.eth.Contract(bucketAbi, address);
return bucket; return bucket;
@ -124,16 +129,16 @@ export const loadGift = (bucketAddress: string, recipientAddress: string) => {
return async (dispatch: Dispatch, getState: () => RootState) => { return async (dispatch: Dispatch, getState: () => RootState) => {
dispatch(loadingGift(bucketAddress, recipientAddress)); dispatch(loadingGift(bucketAddress, recipientAddress));
const bucket = newBucketContract(bucketAddress); const bucket = newBucketContract(bucketAddress);
const expirationTime = await bucket.methods.expirationTime().call();
bucket.methods.gifts(recipientAddress).call().then((result: Any) => { bucket.methods.gifts(recipientAddress).call().then((result: any) => {
const { recipient, amount, code } = result; const { recipient, amount, code } = result;
if (amount === "0") { if (amount === "0") {
dispatch(giftNotFound()) dispatch(giftNotFound())
return; return;
} }
dispatch(giftLoaded(recipient, amount, code)); dispatch(giftLoaded(expirationTime, recipient, amount, code));
dispatch(loadToken(bucket)) dispatch<any>(loadToken(bucket))
}).catch(err => { }).catch(err => {
dispatch(errorLoadingGift(err)) dispatch(errorLoadingGift(err))
console.error("err: ", err) console.error("err: ", err)

206
app/js/actions/redeem.ts Normal file
View File

@ -0,0 +1,206 @@
import { RootState } from '../reducers';
import GiftBucket from '../../../embarkArtifacts/contracts/GiftBucket';
import IERC20Detailed from '../../../embarkArtifacts/contracts/IERC20Detailed';
import { config } from "../config";
import { Dispatch } from 'redux';
import { newBucketContract } from "./bucket";
import { sha3 } from "web3-utils";
import { recoverTypedSignature } from 'eth-sig-util';
import { Web3Type } from "../actions/web3";
const sleep = (ms: number) => {
return new Promise(resolve => {
window.setTimeout(resolve, ms);
});
}
interface RedeemMessage {
receiver: string
code: string
}
export const ERROR_REDEEMING = "ERROR_REDEEMING";
export interface ErrRedeeming {
type: typeof ERROR_REDEEMING
message: string
}
export const ERROR_WRONG_SIGNER = "ERROR_WRONG_SIGNER";
export interface ErrWrongSigner {
type: typeof ERROR_WRONG_SIGNER
expected: string
actual: string
}
export type RedeemErrors =
ErrRedeeming |
ErrWrongSigner;
export const REDEEM_LOADING = "REDEEM_LOADING";
export interface RedeemLoadingAction {
type: typeof REDEEM_LOADING
}
export const REDEEM_ERROR = "REDEEM_ERROR";
export interface RedeemErrorAction {
type: typeof REDEEM_ERROR
error: RedeemErrors
}
export const REDEEM_DONE = "REDEEM_DONE";
export interface RedeemDoneAction {
type: typeof REDEEM_DONE
txHash: string
}
export type RedeemActions =
RedeemLoadingAction |
RedeemErrorAction |
RedeemDoneAction;
const redeeming = () => ({
type: REDEEM_LOADING,
});
const wrongSigner = (expected: string, actual: string) => ({
type: REDEEM_ERROR,
error: {
type: ERROR_WRONG_SIGNER,
expected,
actual,
}
});
const redeemError = (message: string) => ({
type: REDEEM_ERROR,
error: {
type: ERROR_REDEEMING,
message,
}
});
const redeemDone = (txHash: string) => ({
type: REDEEM_DONE,
txHash,
});
export const redeem = (bucketAddress: string, recipientAddress: string, code: string) => {
return (dispatch: Dispatch, getState: () => RootState) => {
dispatch(redeeming());
const state = getState();
const web3Type = state.web3.type;
const bucketAddress = state.bucket.address;
const bucket = newBucketContract(bucketAddress);
const codeHash = sha3(code);
const account = state.web3.account;
const message = {
receiver: state.web3.account,
code: codeHash,
};
//FIXME: is signer needed?
signRedeem(web3Type, bucketAddress, state.web3.account, message).then(async ({ sig, address }: SignRedeemResponse) => {
const recipient = state.bucket.recipient;
//FIXME: remove! hack to wait for the request screen to slide down
await sleep(3000);
if (address.toLowerCase() != recipient.toLowerCase()) {
//FIXME: handle error
dispatch(wrongSigner(recipient, address));
return;
}
const redeem = bucket.methods.redeem(message, sig);
const gas = await redeem.estimateGas();
redeem.send({ from: account, gas }).then(resp => {
dispatch(redeemDone(resp.transactionHash));
}).catch(err => {
console.error("redeem error: ", err);
dispatch(redeemError(err))
});
}).catch(err => {
console.error("sign redeem error: ", err);
dispatch(redeemError(err))
});
}
}
interface SignRedeemResponse {
sig: string
address: string
}
async function signRedeem(web3Type: Web3Type, contractAddress: string, signer: string, message: RedeemMessage): Promise<SignRedeemResponse> {
const chainId = await config.web3!.eth.net.getId();
const domain = [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
];
const redeem = [
{ name: "receiver", type: "address" },
{ name: "code", type: "bytes32" },
];
const domainData = {
name: "KeycardGift",
version: "1",
chainId: chainId,
verifyingContract: contractAddress
};
const data = {
types: {
EIP712Domain: domain,
Redeem: redeem,
},
primaryType: ("Redeem" as const),
domain: domainData,
message: message
};
if (web3Type === Web3Type.Status) {
return signWithKeycard(signer, data);
} else {
return signWithWeb3(signer, data);
}
}
const signWithWeb3 = (signer: string, data: any) => {
return new Promise((resolve, reject) => {
(window as any).ethereum.sendAsync({
method: "eth_signTypedData_v3",
params: [signer, JSON.stringify(data)],
from: signer,
}, (err, resp) => {
if (err) {
reject(err);
} else {
const sig = resp.result;
const address = recoverTypedSignature({
data,
sig
});
resolve({ sig, address });
}
})
});
}
const signWithKeycard = (signer: string, data: any) => {
return new Promise((resolve, reject) => {
(window as any).ethereum.send("keycard_signTypedData", [signer, JSON.stringify(data)]).then(resp => {
const sig = resp.result;
const address = recoverTypedSignature({
data,
sig
});
resolve({ sig, address });
}).catch(err => {
reject(err);
})
});
}

View File

@ -11,10 +11,11 @@ export const VALID_NETWORK_ID = 3;
// export const VALID_NETWORK_ID = 5; // export const VALID_NETWORK_ID = 5;
export const LOCAL_NETWORK_ID = 1337; export const LOCAL_NETWORK_ID = 1337;
enum Web3Type { export enum Web3Type {
None,
Generic, Generic,
Remote, Remote,
Status, Status
} }
export const WEB3_INITIALIZED = "WEB3_INITIALIZED"; export const WEB3_INITIALIZED = "WEB3_INITIALIZED";

View File

@ -1,9 +1,24 @@
import React from 'react'; import React from 'react';
import GiftBucketFactory from '../../../embarkArtifacts/contracts/GiftBucketFactory';
import { import {
shallowEqual, shallowEqual,
useSelector, useSelector,
useDispatch, useDispatch,
} from 'react-redux'; } from 'react-redux';
import { Web3Type } from "../actions/web3";
const web3Type = (t: Web3Type) => {
switch (t) {
case Web3Type.None:
return "not a web3 browser";
case Web3Type.Generic:
return "generic web3 browser";
case Web3Type.Remote:
return "remote web3 node";
case Web3Type.Status:
return "status web3 browser";
}
}
export default function(ownProps: any) { export default function(ownProps: any) {
const props = useSelector(state => { const props = useSelector(state => {
@ -11,6 +26,7 @@ export default function(ownProps: any) {
initialized: state.web3.networkID, initialized: state.web3.networkID,
networkID: state.web3.networkID, networkID: state.web3.networkID,
error: state.web3.error, error: state.web3.error,
type: state.web3.type,
} }
}, shallowEqual); }, shallowEqual);
@ -22,5 +38,13 @@ export default function(ownProps: any) {
return "initializing..."; return "initializing...";
} }
return ownProps.children; return <>
Network ID: {props.networkID} <br />
Factory: {GiftBucketFactory.address} <br />
Web3 Type: {web3Type(props.type)}
<hr />
<div>
{ownProps.children}
</div>
</>;
} }

View File

@ -1,4 +1,5 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { RootState } from '../reducers';
import { useRouteMatch } from 'react-router-dom'; import { useRouteMatch } from 'react-router-dom';
import { import {
shallowEqual, shallowEqual,
@ -8,19 +9,40 @@ import {
import { redeemPath } from '../config'; import { redeemPath } from '../config';
import { import {
loadGift, loadGift,
BucketError, BucketErrors,
ERROR_LOADING_GIFT, ERROR_LOADING_GIFT,
ERROR_GIFT_NOT_FOUND, ERROR_GIFT_NOT_FOUND,
} from '../actions/bucket'; } from '../actions/bucket';
import { toBaseUnit } from "../utils"; import { toBaseUnit } from "../utils";
import {
redeem,
RedeemErrors,
ERROR_REDEEMING,
ERROR_WRONG_SIGNER,
} from '../actions/redeem';
const errorMessage = (error: BucketError): string => { const REDEEM_CODE = "hello world";
const buckerErrorMessage = (error: BucketErrors): string => {
switch (error.type) { switch (error.type) {
case ERROR_LOADING_GIFT: case ERROR_LOADING_GIFT:
return "couldn't load gift."; return "couldn't load gift";
case ERROR_GIFT_NOT_FOUND: case ERROR_GIFT_NOT_FOUND:
return "gift not found"; return "gift not found or already redeemed";
default:
return "something went wrong";
}
}
const redeemErrorMessage = (error: RedeemErrors): string => {
switch (error.type) {
case ERROR_WRONG_SIGNER:
return `wrong signer. expected signature from ${error.expected}, got signature from ${error.actual}`;
case ERROR_REDEEMING:
return `redeem error: ${error.message}`;
default: default:
return "something went wrong"; return "something went wrong";
@ -37,11 +59,11 @@ export default function(ownProps: any) {
const bucketAddress = match.params.bucketAddress; const bucketAddress = match.params.bucketAddress;
const recipientAddress = match.params.recipientAddress; const recipientAddress = match.params.recipientAddress;
const props = useSelector(state => { const props = useSelector((state: RootState) => {
return { return {
bucketAddress: state.bucket.address, bucketAddress: state.bucket.address,
loading: state.bucket.loading, loading: state.bucket.loading,
found: state.bucket.found, expirationTime: state.bucket.expirationTime,
error: state.bucket.error, error: state.bucket.error,
recipient: state.bucket.recipient, recipient: state.bucket.recipient,
amount: state.bucket.amount, amount: state.bucket.amount,
@ -50,6 +72,9 @@ export default function(ownProps: any) {
tokenSymbol: state.bucket.tokenSymbol, tokenSymbol: state.bucket.tokenSymbol,
tokenDecimals: state.bucket.tokenDecimals, tokenDecimals: state.bucket.tokenDecimals,
receiver: state.web3.account, receiver: state.web3.account,
redeeming: state.redeem.loading,
redeemError: state.redeem.error,
redeemTxHash: state.redeem.txHash,
} }
}, shallowEqual); }, shallowEqual);
@ -58,7 +83,7 @@ export default function(ownProps: any) {
}, [bucketAddress, recipientAddress]); }, [bucketAddress, recipientAddress]);
if (props.error) { if (props.error) {
return `Error: ${errorMessage(props.error)}`; return `Error: ${buckerErrorMessage(props.error)}`;
} }
if (props.loading) { if (props.loading) {
@ -75,6 +100,7 @@ export default function(ownProps: any) {
Bucket Address: {props.bucketAddress}<br /> Bucket Address: {props.bucketAddress}<br />
Recipient: {props.recipient}<br /> Recipient: {props.recipient}<br />
Amount: {props.amount}<br /> Amount: {props.amount}<br />
Expiration Time: {new Date(props.expirationTime * 1000).toLocaleDateString("default", {hour: "numeric", minute: "numeric"})}<br />
Code Hash: {props.codeHash}<br /> Code Hash: {props.codeHash}<br />
Token Address: {props.tokenAddress}<br /> Token Address: {props.tokenAddress}<br />
Token Symbol: {props.tokenSymbol}<br /> Token Symbol: {props.tokenSymbol}<br />
@ -84,5 +110,14 @@ export default function(ownProps: any) {
Receiver: {props.receiver} <br /> Receiver: {props.receiver} <br />
<br /><br /><br /> <br /><br /><br />
<button
disabled={props.redeeming}
onClick={() => dispatch(redeem(bucketAddress, recipientAddress, REDEEM_CODE))}>
{props.redeeming ? "Redeeming..." : "Redeem"}
</button>
<br />
{props.redeemError && `Error: ${redeemErrorMessage(props.redeemError)}`}
{props.redeemTxHash && `Done! Tx Hash: ${props.redeemTxHash}`}
</>; </>;
} }

View File

@ -1,6 +1,6 @@
import { import {
BucketActions, BucketActions,
BucketError, BucketErrors,
BUCKET_GIFT_LOADING, BUCKET_GIFT_LOADING,
BUCKET_GIFT_NOT_FOUND, BUCKET_GIFT_NOT_FOUND,
BUCKET_GIFT_LOADED, BUCKET_GIFT_LOADED,
@ -11,9 +11,11 @@ import {
export interface BucketState { export interface BucketState {
loading: boolean loading: boolean
address: string | undefined address: string | undefined
expirationTime: number | undefined
tokenAddress: string | undefined tokenAddress: string | undefined
tokenSymbol: string | undefined
tokenDecimals: number | undefined tokenDecimals: number | undefined
error: BucketState | undefined error: BucketErrors | undefined
recipient: string | undefined recipient: string | undefined
amount: string | undefined amount: string | undefined
codeHash: string | undefined codeHash: string | undefined
@ -22,7 +24,9 @@ export interface BucketState {
const initialState: BucketState = { const initialState: BucketState = {
loading: false, loading: false,
address: undefined, address: undefined,
expirationTime: undefined,
tokenAddress: undefined, tokenAddress: undefined,
tokenSymbol: undefined,
tokenDecimals: undefined, tokenDecimals: undefined,
error: undefined, error: undefined,
recipient: undefined, recipient: undefined,
@ -37,6 +41,7 @@ export const bucketReducer = (state: BucketState = initialState, action: BucketA
...initialState, ...initialState,
loading: true, loading: true,
address: action.address, address: action.address,
recipient: action.recipient,
} }
} }
@ -52,6 +57,7 @@ export const bucketReducer = (state: BucketState = initialState, action: BucketA
return { return {
...state, ...state,
loading: false, loading: false,
expirationTime: action.expirationTime,
recipient: action.recipient, recipient: action.recipient,
amount: action.amount, amount: action.amount,
codeHash: action.codeHash, codeHash: action.codeHash,

View File

@ -8,10 +8,15 @@ import {
BucketState, BucketState,
bucketReducer, bucketReducer,
} from './bucket'; } from './bucket';
import {
RedeemState,
redeemReducer,
} from './redeem';
export interface RootState { export interface RootState {
web3: Web3State, web3: Web3State,
bucket: BucketState, bucket: BucketState,
redeem: RedeemState,
} }
export default function(history) { export default function(history) {
@ -19,5 +24,6 @@ export default function(history) {
web3: web3Reducer, web3: web3Reducer,
router: connectRouter(history), router: connectRouter(history),
bucket: bucketReducer, bucket: bucketReducer,
redeem: redeemReducer,
}); });
} }

60
app/js/reducers/redeem.ts Normal file
View File

@ -0,0 +1,60 @@
import {
RedeemActions,
RedeemErrors,
REDEEM_LOADING,
REDEEM_ERROR,
REDEEM_DONE,
} from "../actions/redeem";
import {
BucketGiftLoadingAction,
BUCKET_GIFT_LOADING
} from "../actions/bucket";
export interface RedeemState {
loading: boolean
error: RedeemErrors | undefined
txHash: string | undefined
receiver: string | undefined
}
const initialState: RedeemState = {
loading: false,
error: undefined,
txHash: undefined,
receiver: undefined,
}
export const redeemReducer = (state: RedeemState = initialState, action: RedeemActions | BucketGiftLoadingAction): RedeemState => {
switch (action.type) {
case BUCKET_GIFT_LOADING: {
return initialState;
}
case REDEEM_LOADING: {
return {
...initialState,
loading: true,
}
}
case REDEEM_ERROR: {
return {
...initialState,
loading: false,
error: action.error,
}
}
case REDEEM_DONE: {
return {
...initialState,
loading: false,
txHash: action.txHash,
}
}
default:
return state;
}
}

View File

@ -4,6 +4,7 @@ import {
WEB3_ERROR, WEB3_ERROR,
WEB3_NETWORK_ID_LOADED, WEB3_NETWORK_ID_LOADED,
WEB3_ACCOUNT_LOADED, WEB3_ACCOUNT_LOADED,
Web3Type,
} from '../actions/web3'; } from '../actions/web3';
export interface Web3State { export interface Web3State {
@ -11,6 +12,7 @@ export interface Web3State {
networkID: number | undefined networkID: number | undefined
error: string | undefined error: string | undefined
account: string | undefined account: string | undefined
type: Web3Type
} }
const initialState: Web3State = { const initialState: Web3State = {
@ -18,6 +20,7 @@ const initialState: Web3State = {
networkID: undefined, networkID: undefined,
error: undefined, error: undefined,
account: undefined, account: undefined,
type: Web3Type.None,
}; };
export const web3Reducer = (state: Web3State = initialState, action: Web3Actions): Web3State => { export const web3Reducer = (state: Web3State = initialState, action: Web3Actions): Web3State => {
@ -26,6 +29,7 @@ export const web3Reducer = (state: Web3State = initialState, action: Web3Actions
return { return {
...state, ...state,
initialized: true, initialized: true,
type: action.web3Type,
} }
} }

View File

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"connected-react-router": "^6.7.0", "connected-react-router": "^6.7.0",
"esm": "^3.2.25", "esm": "^3.2.25",
"eth-sig-util": "^2.5.3",
"history": "^4.10.1", "history": "^4.10.1",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"react": "^16.12.0", "react": "^16.12.0",

View File

@ -43,29 +43,29 @@ let sendMethod;
async function signRedeem(contractAddress, signer, message) { async function signRedeem(contractAddress, signer, message) {
const result = await web3.eth.net.getId(); const result = await web3.eth.net.getId();
let chainId = parseInt(result); let chainId = parseInt(result);
//FIXME: getChainID in the contract returns 1 so we hardcode it here to 1. //FIXME: in tests, getChainID in the contract returns 1 so we hardcode it here to 1.
chainId = 1; chainId = 1;
let domain = [ const domain = [
{ name: "name", type: "string" }, { name: "name", type: "string" },
{ name: "version", type: "string" }, { name: "version", type: "string" },
{ name: "chainId", type: "uint256" }, { name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" } { name: "verifyingContract", type: "address" }
]; ];
let redeem = [ const redeem = [
{ name: "receiver", type: "address" }, { name: "receiver", type: "address" },
{ name: "code", type: "bytes32" }, { name: "code", type: "bytes32" },
]; ];
let domainData = { const domainData = {
name: "KeycardGift", name: "KeycardGift",
version: "1", version: "1",
chainId: chainId, chainId: chainId,
verifyingContract: contractAddress verifyingContract: contractAddress
}; };
let data = { const data = {
types: { types: {
EIP712Domain: domain, EIP712Domain: domain,
Redeem: redeem, Redeem: redeem,

View File

@ -4881,6 +4881,18 @@ eth-sig-util@^2.1.1:
tweetnacl "^1.0.0" tweetnacl "^1.0.0"
tweetnacl-util "^0.15.0" tweetnacl-util "^0.15.0"
eth-sig-util@^2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-2.5.3.tgz#6938308b38226e0b3085435474900b03036abcbe"
integrity sha512-KpXbCKmmBUNUTGh9MRKmNkIPietfhzBqqYqysDavLseIiMUGl95k6UcPEkALAZlj41e9E6yioYXc1PC333RKqw==
dependencies:
buffer "^5.2.1"
elliptic "^6.4.0"
ethereumjs-abi "0.6.5"
ethereumjs-util "^5.1.1"
tweetnacl "^1.0.0"
tweetnacl-util "^0.15.0"
ethashjs@~0.0.7: ethashjs@~0.0.7:
version "0.0.7" version "0.0.7"
resolved "https://registry.yarnpkg.com/ethashjs/-/ethashjs-0.0.7.tgz#30bfe4196726690a0c59d3b8272e70d4d0c34bae" resolved "https://registry.yarnpkg.com/ethashjs/-/ethashjs-0.0.7.tgz#30bfe4196726690a0c59d3b8272e70d4d0c34bae"