add redeem actions
This commit is contained in:
parent
3fbbffca65
commit
5d9e3bcb6c
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
|
@ -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";
|
||||||
|
|
|
@ -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>
|
||||||
|
</>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}`}
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue