feat: add express server to serve React build and update blockchain utility for token approval

This commit is contained in:
2026-01-05 13:55:37 +09:00
parent 108b78bf64
commit 729d2a7fd3
16 changed files with 114 additions and 12 deletions

3
.gitignore vendored
View File

@@ -10,9 +10,6 @@ package-lock.json
# testing # testing
/coverage /coverage
# production
/build
# misc # misc
.DS_Store .DS_Store
.env.local .env.local

15
build/asset-manifest.json Normal file
View File

@@ -0,0 +1,15 @@
{
"files": {
"main.css": "/static/css/main.6d74c8c6.css",
"main.js": "/static/js/main.040a5bac.js",
"static/js/453.41fba699.chunk.js": "/static/js/453.41fba699.chunk.js",
"index.html": "/index.html",
"main.6d74c8c6.css.map": "/static/css/main.6d74c8c6.css.map",
"main.040a5bac.js.map": "/static/js/main.040a5bac.js.map",
"453.41fba699.chunk.js.map": "/static/js/453.41fba699.chunk.js.map"
},
"entrypoints": [
"static/css/main.6d74c8c6.css",
"static/js/main.040a5bac.js"
]
}

BIN
build/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

1
build/index.html Normal file
View File

@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Admin MultiSender</title><script defer="defer" src="/static/js/main.040a5bac.js"></script><link href="/static/css/main.6d74c8c6.css" rel="stylesheet"></head><body><div id="root"></div></body></html>

8
build/manifest.json Normal file
View File

@@ -0,0 +1,8 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
build/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -0,0 +1,2 @@
body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;margin:0}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}:root{--gap:20px}*{box-sizing:border-box;margin:0;padding:0}html{font-size:12px;overscroll-behavior:contain;scroll-behavior:smooth;scroll-padding-top:0;scroll-snap-type:y mandatory}body,html{font-family:sans-serif;margin:0}.wrap{align-items:start;display:flex;flex-direction:row;gap:20px;gap:var(--gap);justify-content:center;padding:20px;padding:var(--gap)}.wrap fieldset{border:1px solid #ccc;border-radius:.5rem;flex-grow:1;height:calc(100vh - 80px);overflow:auto;padding:1.5rem}.wrap fieldset:first-child{flex-basis:500px;flex-grow:0}.wrap fieldset legend{font-size:1.5rem;font-weight:700;padding:0 2rem 0 .75rem}fieldset ul{list-style:none;padding:0}fieldset ul>li{display:flex;flex-direction:column;margin-bottom:2rem}fieldset div label,fieldset label{font-size:1.2rem;font-weight:700;margin-bottom:.5rem;padding-left:.375rem}fieldset div label+input,fieldset label+input{background-clip:padding-box;background-color:#fff;border:1px solid #ced4da;border-radius:.5rem;color:#495057;display:block;font-size:1rem;font-weight:400;height:3rem;line-height:1.5;padding:.375rem .75rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:100%}fieldset label+input:focus{background-color:#fff;border-color:grey;box-shadow:0 0 0 .2rem #64646440;color:#495057;outline:0}fieldset label+input[type=file]{display:none}fieldset input[type=file]+label{align-items:center;border:2px dotted #ccc;border-radius:.5rem;display:flex;flex-direction:column;height:10rem;justify-content:center}fieldset input[type=file]+label:before{color:#00000080;content:"업로드 할 파일을 선택하거나 드레그&드롭하세요.";font-weight:400;text-shadow:0 0 2px #000000b3}fieldset table{background-color:#aaa;border-collapse:initial;border-color:gray;border-spacing:1px;box-sizing:border-box;caption-side:bottom;display:table;font-size:1.2rem;margin-bottom:30px;width:100%}fieldset thead{background-color:#000000b3;border-bottom:2px solid #000;color:#fff}fieldset th{padding:.375rem .725rem;text-align:center}fieldset td{background-color:#ffffffe6;font-size:1rem;padding:.2rem .5rem}fieldset tr[row-type=sum]{border-top:2px solid #aaa}[data-type=number]{padding:.2rem 1rem;text-align:right}.flex-row{display:flex;flex-direction:row;gap:20px;position:relative}.flex-row>table{flex-grow:1}@media screen and (max-width:1200px){.wrap{flex-direction:column}.wrap fieldset{flex-basis:auto;flex-grow:1!important;height:auto;width:100%}.flex-row{flex-direction:column}}.transferButton{font-size:20px;margin-top:20px;padding:8px;width:100px}.App{text-align:center}.App-logo{height:40vmin;pointer-events:none}@media (prefers-reduced-motion:no-preference){.App-logo{animation:App-logo-spin 20s linear infinite}}.App-header{align-items:center;background-color:#282c34;color:#fff;display:flex;flex-direction:column;font-size:calc(10px + 2vmin);justify-content:center;min-height:100vh}.App-link{color:#61dafb}@keyframes App-logo-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}
/*# sourceMappingURL=main.6d74c8c6.css.map*/

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
"use strict";(self.webpackChunkadminsite=self.webpackChunkadminsite||[]).push([[453],{453:(e,t,n)=>{n.r(t),n.d(t,{getCLS:()=>y,getFCP:()=>g,getFID:()=>C,getLCP:()=>P,getTTFB:()=>D});var i,r,a,o,u=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver(function(e){return e.getEntries().map(t)});return n.observe({type:e,buffered:!0}),n}}catch(e){}},s=function(e,t){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),t&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},f=function(e){addEventListener("pageshow",function(t){t.persisted&&e(t)},!0)},m=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},v=-1,d=function(){return"hidden"===document.visibilityState?0:1/0},p=function(){s(function(e){var t=e.timeStamp;v=t},!0)},l=function(){return v<0&&(v=d(),p(),f(function(){setTimeout(function(){v=d(),p()},0)})),{get firstHiddenTime(){return v}}},g=function(e,t){var n,i=l(),r=u("FCP"),a=function(e){"first-contentful-paint"===e.name&&(s&&s.disconnect(),e.startTime<i.firstHiddenTime&&(r.value=e.startTime,r.entries.push(e),n(!0)))},o=window.performance&&performance.getEntriesByName&&performance.getEntriesByName("first-contentful-paint")[0],s=o?null:c("paint",a);(o||s)&&(n=m(e,r,t),o&&a(o),f(function(i){r=u("FCP"),n=m(e,r,t),requestAnimationFrame(function(){requestAnimationFrame(function(){r.value=performance.now()-i.timeStamp,n(!0)})})}))},h=!1,T=-1,y=function(e,t){h||(g(function(e){T=e.value}),h=!0);var n,i=function(t){T>-1&&e(t)},r=u("CLS",0),a=0,o=[],v=function(e){if(!e.hadRecentInput){var t=o[0],i=o[o.length-1];a&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(a+=e.value,o.push(e)):(a=e.value,o=[e]),a>r.value&&(r.value=a,r.entries=o,n())}},d=c("layout-shift",v);d&&(n=m(i,r,t),s(function(){d.takeRecords().map(v),n(!0)}),f(function(){a=0,T=-1,r=u("CLS",0),n=m(i,r,t)}))},E={passive:!0,capture:!0},w=new Date,L=function(e,t){i||(i=t,r=e,a=new Date,F(removeEventListener),S())},S=function(){if(r>=0&&r<a-w){var e={entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+r};o.forEach(function(t){t(e)}),o=[]}},b=function(e){if(e.cancelable){var t=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){L(e,t),r()},i=function(){r()},r=function(){removeEventListener("pointerup",n,E),removeEventListener("pointercancel",i,E)};addEventListener("pointerup",n,E),addEventListener("pointercancel",i,E)}(t,e):L(t,e)}},F=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach(function(t){return e(t,b,E)})},C=function(e,t){var n,a=l(),v=u("FID"),d=function(e){e.startTime<a.firstHiddenTime&&(v.value=e.processingStart-e.startTime,v.entries.push(e),n(!0))},p=c("first-input",d);n=m(e,v,t),p&&s(function(){p.takeRecords().map(d),p.disconnect()},!0),p&&f(function(){var a;v=u("FID"),n=m(e,v,t),o=[],r=-1,i=null,F(addEventListener),a=d,o.push(a),S()})},k={},P=function(e,t){var n,i=l(),r=u("LCP"),a=function(e){var t=e.startTime;t<i.firstHiddenTime&&(r.value=t,r.entries.push(e),n())},o=c("largest-contentful-paint",a);if(o){n=m(e,r,t);var v=function(){k[r.id]||(o.takeRecords().map(a),o.disconnect(),k[r.id]=!0,n(!0))};["keydown","click"].forEach(function(e){addEventListener(e,v,{once:!0,capture:!0})}),s(v,!0),f(function(i){r=u("LCP"),n=m(e,r,t),requestAnimationFrame(function(){requestAnimationFrame(function(){r.value=performance.now()-i.timeStamp,k[r.id]=!0,n(!0)})})})}},D=function(e){var t,n=u("TTFB");t=function(){try{var t=performance.getEntriesByType("navigation")[0]||function(){var e=performance.timing,t={entryType:"navigation",startTime:0};for(var n in e)"navigationStart"!==n&&"toJSON"!==n&&(t[n]=Math.max(e[n]-e.navigationStart,0));return t}();if(n.value=n.delta=t.responseStart,n.value<0||n.value>performance.now())return;n.entries=[t],e(n)}catch(e){}},"complete"===document.readyState?setTimeout(t,0):addEventListener("load",function(){return setTimeout(t,0)})}}}]);
//# sourceMappingURL=453.41fba699.chunk.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,55 @@
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
/*! sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */
/*! xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
/**
* @license React
* react-dom-client.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-dom.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-jsx-runtime.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

View File

@@ -8,6 +8,7 @@
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"ethers": "^6.15.0", "ethers": "^6.15.0",
"express": "^5.2.1",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",

12
server.js Normal file
View File

@@ -0,0 +1,12 @@
const express = require('express');
const path = require('path');
const app = express();
app.use(express.static(path.join(__dirname, 'build')));
app.use((req, res) => {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.listen(3000, () => {
console.log('React build serving on 3000');
});

View File

@@ -21,17 +21,17 @@ export const multisend = async (tokenAddress, provider, from, toArray, amountArr
{ {
try { try {
const tokenContract = new ethers.Contract(tokenAddress, tokenAbi, signer); const tokenContract = new ethers.Contract(tokenAddress, tokenAbi, signer);
let allowance = isToken ? await tokenContract.allowance(from, contractAddress) : 0;
let balance = isToken ? await tokenContract.balanceOf(from) : await provider.getBalance(from); let balance = isToken ? await tokenContract.balanceOf(from) : await provider.getBalance(from);
if(balance >= parseEther(totalAmount.toString())){ if(balance >= parseEther(totalAmount.toString())){
if(isToken) if( isToken && allowance < parseEther(totalAmount.toString()))
{ {
////token approve const tx = await tokenContract.approve(
// const tx = await tokenContract.approve( contractAddress,
// contractAddress, parseEther(totalAmount.toString())
// parseEther(totalAmount.toString()) )
// ) await provider.waitForTransaction(tx.hash);
// await provider.waitForTransaction(tx.hash);
} }
} else { } else {
throw new Error("잔액부족") throw new Error("잔액부족")