diff --git a/.manus/db/db-query-1772939157220.json b/.manus/db/db-query-1772939157220.json new file mode 100644 index 0000000..22d1e1d --- /dev/null +++ b/.manus/db/db-query-1772939157220.json @@ -0,0 +1,106 @@ +{ + "query": "DESCRIBE trc20_purchases;", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway03.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 3Bq4cgN2KNKQqNu.8160cd2033e0 --database Ngki3MumDNGduV3xJt3mga --execute DESCRIBE trc20_purchases;", + "rows": [ + { + "Field": "id", + "Type": "int", + "Null": "NO", + "Key": "PRI", + "Default": "NULL", + "Extra": "auto_increment" + }, + { + "Field": "txHash", + "Type": "varchar(128)", + "Null": "NO", + "Key": "UNI", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "fromAddress", + "Type": "varchar(64)", + "Null": "NO", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "usdtAmount", + "Type": "decimal(20,6)", + "Null": "NO", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "xicAmount", + "Type": "decimal(30,6)", + "Null": "NO", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "blockNumber", + "Type": "bigint", + "Null": "YES", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "status", + "Type": "enum('pending','confirmed','distributed','failed')", + "Null": "NO", + "Key": "", + "Default": "pending", + "Extra": "" + }, + { + "Field": "distributedAt", + "Type": "timestamp", + "Null": "YES", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "distributeTxHash", + "Type": "varchar(128)", + "Null": "YES", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "createdAt", + "Type": "timestamp", + "Null": "NO", + "Key": "", + "Default": "CURRENT_TIMESTAMP", + "Extra": "" + }, + { + "Field": "updatedAt", + "Type": "timestamp", + "Null": "NO", + "Key": "", + "Default": "CURRENT_TIMESTAMP", + "Extra": "DEFAULT_GENERATED on update CURRENT_TIMESTAMP" + }, + { + "Field": "evmAddress", + "Type": "varchar(64)", + "Null": "YES", + "Key": "", + "Default": "NULL", + "Extra": "" + } + ], + "messages": [], + "stdout": "Field\tType\tNull\tKey\tDefault\tExtra\nid\tint\tNO\tPRI\tNULL\tauto_increment\ntxHash\tvarchar(128)\tNO\tUNI\tNULL\t\nfromAddress\tvarchar(64)\tNO\t\tNULL\t\nusdtAmount\tdecimal(20,6)\tNO\t\tNULL\t\nxicAmount\tdecimal(30,6)\tNO\t\tNULL\t\nblockNumber\tbigint\tYES\t\tNULL\t\nstatus\tenum('pending','confirmed','distributed','failed')\tNO\t\tpending\t\ndistributedAt\ttimestamp\tYES\t\tNULL\t\ndistributeTxHash\tvarchar(128)\tYES\t\tNULL\t\ncreatedAt\ttimestamp\tNO\t\tCURRENT_TIMESTAMP\t\nupdatedAt\ttimestamp\tNO\t\tCURRENT_TIMESTAMP\tDEFAULT_GENERATED on update CURRENT_TIMESTAMP\nevmAddress\tvarchar(64)\tYES\t\tNULL\t\n", + "stderr": "", + "execution_time_ms": 1590 +} \ No newline at end of file diff --git a/.manus/db/db-query-1772939162890.json b/.manus/db/db-query-1772939162890.json new file mode 100644 index 0000000..b653ef5 --- /dev/null +++ b/.manus/db/db-query-1772939162890.json @@ -0,0 +1,58 @@ +{ + "query": "SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'trc20_purchases' ORDER BY ORDINAL_POSITION;", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway03.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 3Bq4cgN2KNKQqNu.8160cd2033e0 --database Ngki3MumDNGduV3xJt3mga --execute SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'trc20_purchases' ORDER BY ORDINAL_POSITION;", + "rows": [ + { + "COLUMN_NAME": "id", + "DATA_TYPE": "int" + }, + { + "COLUMN_NAME": "txHash", + "DATA_TYPE": "varchar" + }, + { + "COLUMN_NAME": "fromAddress", + "DATA_TYPE": "varchar" + }, + { + "COLUMN_NAME": "usdtAmount", + "DATA_TYPE": "decimal" + }, + { + "COLUMN_NAME": "xicAmount", + "DATA_TYPE": "decimal" + }, + { + "COLUMN_NAME": "blockNumber", + "DATA_TYPE": "bigint" + }, + { + "COLUMN_NAME": "status", + "DATA_TYPE": "enum" + }, + { + "COLUMN_NAME": "distributedAt", + "DATA_TYPE": "timestamp" + }, + { + "COLUMN_NAME": "distributeTxHash", + "DATA_TYPE": "varchar" + }, + { + "COLUMN_NAME": "createdAt", + "DATA_TYPE": "timestamp" + }, + { + "COLUMN_NAME": "updatedAt", + "DATA_TYPE": "timestamp" + }, + { + "COLUMN_NAME": "evmAddress", + "DATA_TYPE": "varchar" + } + ], + "messages": [], + "stdout": "COLUMN_NAME\tDATA_TYPE\nid\tint\ntxHash\tvarchar\nfromAddress\tvarchar\nusdtAmount\tdecimal\nxicAmount\tdecimal\nblockNumber\tbigint\nstatus\tenum\ndistributedAt\ttimestamp\ndistributeTxHash\tvarchar\ncreatedAt\ttimestamp\nupdatedAt\ttimestamp\nevmAddress\tvarchar\n", + "stderr": "", + "execution_time_ms": 1492 +} \ No newline at end of file diff --git a/.manus/db/db-query-1772939166662.json b/.manus/db/db-query-1772939166662.json new file mode 100644 index 0000000..d504e87 --- /dev/null +++ b/.manus/db/db-query-1772939166662.json @@ -0,0 +1,106 @@ +{ + "query": "SHOW COLUMNS FROM trc20_purchases;", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway03.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 3Bq4cgN2KNKQqNu.8160cd2033e0 --database Ngki3MumDNGduV3xJt3mga --execute SHOW COLUMNS FROM trc20_purchases;", + "rows": [ + { + "Field": "id", + "Type": "int", + "Null": "NO", + "Key": "PRI", + "Default": "NULL", + "Extra": "auto_increment" + }, + { + "Field": "txHash", + "Type": "varchar(128)", + "Null": "NO", + "Key": "UNI", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "fromAddress", + "Type": "varchar(64)", + "Null": "NO", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "usdtAmount", + "Type": "decimal(20,6)", + "Null": "NO", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "xicAmount", + "Type": "decimal(30,6)", + "Null": "NO", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "blockNumber", + "Type": "bigint", + "Null": "YES", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "status", + "Type": "enum('pending','confirmed','distributed','failed')", + "Null": "NO", + "Key": "", + "Default": "pending", + "Extra": "" + }, + { + "Field": "distributedAt", + "Type": "timestamp", + "Null": "YES", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "distributeTxHash", + "Type": "varchar(128)", + "Null": "YES", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "createdAt", + "Type": "timestamp", + "Null": "NO", + "Key": "", + "Default": "CURRENT_TIMESTAMP", + "Extra": "" + }, + { + "Field": "updatedAt", + "Type": "timestamp", + "Null": "NO", + "Key": "", + "Default": "CURRENT_TIMESTAMP", + "Extra": "DEFAULT_GENERATED on update CURRENT_TIMESTAMP" + }, + { + "Field": "evmAddress", + "Type": "varchar(64)", + "Null": "YES", + "Key": "", + "Default": "NULL", + "Extra": "" + } + ], + "messages": [], + "stdout": "Field\tType\tNull\tKey\tDefault\tExtra\nid\tint\tNO\tPRI\tNULL\tauto_increment\ntxHash\tvarchar(128)\tNO\tUNI\tNULL\t\nfromAddress\tvarchar(64)\tNO\t\tNULL\t\nusdtAmount\tdecimal(20,6)\tNO\t\tNULL\t\nxicAmount\tdecimal(30,6)\tNO\t\tNULL\t\nblockNumber\tbigint\tYES\t\tNULL\t\nstatus\tenum('pending','confirmed','distributed','failed')\tNO\t\tpending\t\ndistributedAt\ttimestamp\tYES\t\tNULL\t\ndistributeTxHash\tvarchar(128)\tYES\t\tNULL\t\ncreatedAt\ttimestamp\tNO\t\tCURRENT_TIMESTAMP\t\nupdatedAt\ttimestamp\tNO\t\tCURRENT_TIMESTAMP\tDEFAULT_GENERATED on update CURRENT_TIMESTAMP\nevmAddress\tvarchar(64)\tYES\t\tNULL\t\n", + "stderr": "", + "execution_time_ms": 1499 +} \ No newline at end of file diff --git a/.manus/db/db-query-1772939179948.json b/.manus/db/db-query-1772939179948.json new file mode 100644 index 0000000..8c9ca8a --- /dev/null +++ b/.manus/db/db-query-1772939179948.json @@ -0,0 +1,13 @@ +{ + "query": "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'trc20_purchases' AND COLUMN_NAME LIKE '%vm%';", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway03.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 3Bq4cgN2KNKQqNu.8160cd2033e0 --database Ngki3MumDNGduV3xJt3mga --execute SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'trc20_purchases' AND COLUMN_NAME LIKE '%vm%';", + "rows": [ + { + "COLUMN_NAME": "evmAddress" + } + ], + "messages": [], + "stdout": "COLUMN_NAME\nevmAddress\n", + "stderr": "", + "execution_time_ms": 1512 +} \ No newline at end of file diff --git a/.manus/db/db-query-1772939183876.json b/.manus/db/db-query-1772939183876.json new file mode 100644 index 0000000..deca464 --- /dev/null +++ b/.manus/db/db-query-1772939183876.json @@ -0,0 +1,46 @@ +{ + "query": "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'trc20_purchases';", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway03.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 3Bq4cgN2KNKQqNu.8160cd2033e0 --database Ngki3MumDNGduV3xJt3mga --execute SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'trc20_purchases';", + "rows": [ + { + "COLUMN_NAME": "id" + }, + { + "COLUMN_NAME": "txHash" + }, + { + "COLUMN_NAME": "fromAddress" + }, + { + "COLUMN_NAME": "usdtAmount" + }, + { + "COLUMN_NAME": "xicAmount" + }, + { + "COLUMN_NAME": "blockNumber" + }, + { + "COLUMN_NAME": "status" + }, + { + "COLUMN_NAME": "distributedAt" + }, + { + "COLUMN_NAME": "distributeTxHash" + }, + { + "COLUMN_NAME": "createdAt" + }, + { + "COLUMN_NAME": "updatedAt" + }, + { + "COLUMN_NAME": "evmAddress" + } + ], + "messages": [], + "stdout": "COLUMN_NAME\nid\ntxHash\nfromAddress\nusdtAmount\nxicAmount\nblockNumber\nstatus\ndistributedAt\ndistributeTxHash\ncreatedAt\nupdatedAt\nevmAddress\n", + "stderr": "", + "execution_time_ms": 1493 +} \ No newline at end of file diff --git a/.manus/db/db-query-1772939235354.json b/.manus/db/db-query-1772939235354.json new file mode 100644 index 0000000..ff2f2d8 --- /dev/null +++ b/.manus/db/db-query-1772939235354.json @@ -0,0 +1,58 @@ +{ + "query": "SELECT COLUMN_NAME, COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'trc20_purchases' ORDER BY ORDINAL_POSITION;", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway03.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 3Bq4cgN2KNKQqNu.8160cd2033e0 --database Ngki3MumDNGduV3xJt3mga --execute SELECT COLUMN_NAME, COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'trc20_purchases' ORDER BY ORDINAL_POSITION;", + "rows": [ + { + "COLUMN_NAME": "id", + "COLUMN_TYPE": "int" + }, + { + "COLUMN_NAME": "txHash", + "COLUMN_TYPE": "varchar(128)" + }, + { + "COLUMN_NAME": "fromAddress", + "COLUMN_TYPE": "varchar(64)" + }, + { + "COLUMN_NAME": "usdtAmount", + "COLUMN_TYPE": "decimal(20,6)" + }, + { + "COLUMN_NAME": "xicAmount", + "COLUMN_TYPE": "decimal(30,6)" + }, + { + "COLUMN_NAME": "blockNumber", + "COLUMN_TYPE": "bigint" + }, + { + "COLUMN_NAME": "status", + "COLUMN_TYPE": "enum('pending','confirmed','distributed','failed')" + }, + { + "COLUMN_NAME": "distributedAt", + "COLUMN_TYPE": "timestamp" + }, + { + "COLUMN_NAME": "distributeTxHash", + "COLUMN_TYPE": "varchar(128)" + }, + { + "COLUMN_NAME": "createdAt", + "COLUMN_TYPE": "timestamp" + }, + { + "COLUMN_NAME": "updatedAt", + "COLUMN_TYPE": "timestamp" + }, + { + "COLUMN_NAME": "evmAddress", + "COLUMN_TYPE": "varchar(64)" + } + ], + "messages": [], + "stdout": "COLUMN_NAME\tCOLUMN_TYPE\nid\tint\ntxHash\tvarchar(128)\nfromAddress\tvarchar(64)\nusdtAmount\tdecimal(20,6)\nxicAmount\tdecimal(30,6)\nblockNumber\tbigint\nstatus\tenum('pending','confirmed','distributed','failed')\ndistributedAt\ttimestamp\ndistributeTxHash\tvarchar(128)\ncreatedAt\ttimestamp\nupdatedAt\ttimestamp\nevmAddress\tvarchar(64)\n", + "stderr": "", + "execution_time_ms": 1572 +} \ No newline at end of file diff --git a/.manus/db/db-query-1772939240389.json b/.manus/db/db-query-1772939240389.json new file mode 100644 index 0000000..9498c9b --- /dev/null +++ b/.manus/db/db-query-1772939240389.json @@ -0,0 +1,13 @@ +{ + "query": "SELECT 1 FROM trc20_purchases LIMIT 0; SELECT evmAddress FROM trc20_purchases LIMIT 1;", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway03.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 3Bq4cgN2KNKQqNu.8160cd2033e0 --database Ngki3MumDNGduV3xJt3mga --execute SELECT 1 FROM trc20_purchases LIMIT 0; SELECT evmAddress FROM trc20_purchases LIMIT 1;", + "rows": [ + { + "evmAddress": "NULL" + } + ], + "messages": [], + "stdout": "evmAddress\nNULL\n", + "stderr": "", + "execution_time_ms": 1778 +} \ No newline at end of file diff --git a/.manus/db/db-query-1772939382289.json b/.manus/db/db-query-1772939382289.json new file mode 100644 index 0000000..a31b07b --- /dev/null +++ b/.manus/db/db-query-1772939382289.json @@ -0,0 +1,70 @@ +{ + "query": "SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'trc20_purchases';", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway03.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 3Bq4cgN2KNKQqNu.8160cd2033e0 --database Ngki3MumDNGduV3xJt3mga --execute SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'trc20_purchases';", + "rows": [ + { + "COLUMN_NAME": "id", + "COLUMN_TYPE": "int", + "IS_NULLABLE": "NO" + }, + { + "COLUMN_NAME": "txHash", + "COLUMN_TYPE": "varchar(128)", + "IS_NULLABLE": "NO" + }, + { + "COLUMN_NAME": "fromAddress", + "COLUMN_TYPE": "varchar(64)", + "IS_NULLABLE": "NO" + }, + { + "COLUMN_NAME": "usdtAmount", + "COLUMN_TYPE": "decimal(20,6)", + "IS_NULLABLE": "NO" + }, + { + "COLUMN_NAME": "xicAmount", + "COLUMN_TYPE": "decimal(30,6)", + "IS_NULLABLE": "NO" + }, + { + "COLUMN_NAME": "blockNumber", + "COLUMN_TYPE": "bigint", + "IS_NULLABLE": "YES" + }, + { + "COLUMN_NAME": "status", + "COLUMN_TYPE": "enum('pending','confirmed','distributed','failed')", + "IS_NULLABLE": "NO" + }, + { + "COLUMN_NAME": "distributedAt", + "COLUMN_TYPE": "timestamp", + "IS_NULLABLE": "YES" + }, + { + "COLUMN_NAME": "distributeTxHash", + "COLUMN_TYPE": "varchar(128)", + "IS_NULLABLE": "YES" + }, + { + "COLUMN_NAME": "createdAt", + "COLUMN_TYPE": "timestamp", + "IS_NULLABLE": "NO" + }, + { + "COLUMN_NAME": "updatedAt", + "COLUMN_TYPE": "timestamp", + "IS_NULLABLE": "NO" + }, + { + "COLUMN_NAME": "evmAddress", + "COLUMN_TYPE": "varchar(64)", + "IS_NULLABLE": "YES" + } + ], + "messages": [], + "stdout": "COLUMN_NAME\tCOLUMN_TYPE\tIS_NULLABLE\nid\tint\tNO\ntxHash\tvarchar(128)\tNO\nfromAddress\tvarchar(64)\tNO\nusdtAmount\tdecimal(20,6)\tNO\nxicAmount\tdecimal(30,6)\tNO\nblockNumber\tbigint\tYES\nstatus\tenum('pending','confirmed','distributed','failed')\tNO\ndistributedAt\ttimestamp\tYES\ndistributeTxHash\tvarchar(128)\tYES\ncreatedAt\ttimestamp\tNO\nupdatedAt\ttimestamp\tNO\nevmAddress\tvarchar(64)\tYES\n", + "stderr": "", + "execution_time_ms": 1482 +} \ No newline at end of file diff --git a/.manus/db/db-query-1772939387302.json b/.manus/db/db-query-1772939387302.json new file mode 100644 index 0000000..e47f8ca --- /dev/null +++ b/.manus/db/db-query-1772939387302.json @@ -0,0 +1,13 @@ +{ + "query": "SELECT `evmAddress` FROM trc20_purchases LIMIT 1;", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway03.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 3Bq4cgN2KNKQqNu.8160cd2033e0 --database Ngki3MumDNGduV3xJt3mga --execute SELECT `evmAddress` FROM trc20_purchases LIMIT 1;", + "rows": [ + { + "evmAddress": "NULL" + } + ], + "messages": [], + "stdout": "evmAddress\nNULL\n", + "stderr": "", + "execution_time_ms": 1511 +} \ No newline at end of file diff --git a/.manus/db/db-query-1772939683981.json b/.manus/db/db-query-1772939683981.json new file mode 100644 index 0000000..9923180 --- /dev/null +++ b/.manus/db/db-query-1772939683981.json @@ -0,0 +1,106 @@ +{ + "query": "SHOW COLUMNS FROM trc20_purchases;", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway03.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 3Bq4cgN2KNKQqNu.8160cd2033e0 --database Ngki3MumDNGduV3xJt3mga --execute SHOW COLUMNS FROM trc20_purchases;", + "rows": [ + { + "Field": "id", + "Type": "int", + "Null": "NO", + "Key": "PRI", + "Default": "NULL", + "Extra": "auto_increment" + }, + { + "Field": "txHash", + "Type": "varchar(128)", + "Null": "NO", + "Key": "UNI", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "fromAddress", + "Type": "varchar(64)", + "Null": "NO", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "usdtAmount", + "Type": "decimal(20,6)", + "Null": "NO", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "xicAmount", + "Type": "decimal(30,6)", + "Null": "NO", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "blockNumber", + "Type": "bigint", + "Null": "YES", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "status", + "Type": "enum('pending','confirmed','distributed','failed')", + "Null": "NO", + "Key": "", + "Default": "pending", + "Extra": "" + }, + { + "Field": "distributedAt", + "Type": "timestamp", + "Null": "YES", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "distributeTxHash", + "Type": "varchar(128)", + "Null": "YES", + "Key": "", + "Default": "NULL", + "Extra": "" + }, + { + "Field": "createdAt", + "Type": "timestamp", + "Null": "NO", + "Key": "", + "Default": "CURRENT_TIMESTAMP", + "Extra": "" + }, + { + "Field": "updatedAt", + "Type": "timestamp", + "Null": "NO", + "Key": "", + "Default": "CURRENT_TIMESTAMP", + "Extra": "DEFAULT_GENERATED on update CURRENT_TIMESTAMP" + }, + { + "Field": "evmAddress", + "Type": "varchar(64)", + "Null": "YES", + "Key": "", + "Default": "NULL", + "Extra": "" + } + ], + "messages": [], + "stdout": "Field\tType\tNull\tKey\tDefault\tExtra\nid\tint\tNO\tPRI\tNULL\tauto_increment\ntxHash\tvarchar(128)\tNO\tUNI\tNULL\t\nfromAddress\tvarchar(64)\tNO\t\tNULL\t\nusdtAmount\tdecimal(20,6)\tNO\t\tNULL\t\nxicAmount\tdecimal(30,6)\tNO\t\tNULL\t\nblockNumber\tbigint\tYES\t\tNULL\t\nstatus\tenum('pending','confirmed','distributed','failed')\tNO\t\tpending\t\ndistributedAt\ttimestamp\tYES\t\tNULL\t\ndistributeTxHash\tvarchar(128)\tYES\t\tNULL\t\ncreatedAt\ttimestamp\tNO\t\tCURRENT_TIMESTAMP\t\nupdatedAt\ttimestamp\tNO\t\tCURRENT_TIMESTAMP\tDEFAULT_GENERATED on update CURRENT_TIMESTAMP\nevmAddress\tvarchar(64)\tYES\t\tNULL\t\n", + "stderr": "", + "execution_time_ms": 1594 +} \ No newline at end of file diff --git a/.manus/db/db-query-1772939688136.json b/.manus/db/db-query-1772939688136.json new file mode 100644 index 0000000..1b8d833 --- /dev/null +++ b/.manus/db/db-query-1772939688136.json @@ -0,0 +1,46 @@ +{ + "query": "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'trc20_purchases' ORDER BY ORDINAL_POSITION;", + "command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway03.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 3Bq4cgN2KNKQqNu.8160cd2033e0 --database Ngki3MumDNGduV3xJt3mga --execute SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'trc20_purchases' ORDER BY ORDINAL_POSITION;", + "rows": [ + { + "COLUMN_NAME": "id" + }, + { + "COLUMN_NAME": "txHash" + }, + { + "COLUMN_NAME": "fromAddress" + }, + { + "COLUMN_NAME": "usdtAmount" + }, + { + "COLUMN_NAME": "xicAmount" + }, + { + "COLUMN_NAME": "blockNumber" + }, + { + "COLUMN_NAME": "status" + }, + { + "COLUMN_NAME": "distributedAt" + }, + { + "COLUMN_NAME": "distributeTxHash" + }, + { + "COLUMN_NAME": "createdAt" + }, + { + "COLUMN_NAME": "updatedAt" + }, + { + "COLUMN_NAME": "evmAddress" + } + ], + "messages": [], + "stdout": "COLUMN_NAME\nid\ntxHash\nfromAddress\nusdtAmount\nxicAmount\nblockNumber\nstatus\ndistributedAt\ndistributeTxHash\ncreatedAt\nupdatedAt\nevmAddress\n", + "stderr": "", + "execution_time_ms": 1593 +} \ No newline at end of file diff --git a/client/src/App.tsx b/client/src/App.tsx index e075a06..382c633 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -5,22 +5,35 @@ import { Route, Switch } from "wouter"; import ErrorBoundary from "./components/ErrorBoundary"; import { ThemeProvider } from "./contexts/ThemeContext"; import Home from "./pages/Home"; +import Tutorial from "./pages/Tutorial"; +import Admin from "./pages/Admin"; function Router() { // make sure to consider if you need authentication for certain routes return ( + + + {/* Final fallback route */} ); } +// NOTE: About Theme +// - First choose a default theme according to your design style (dark or light bg), than change color palette in index.css +// to keep consistent foreground/background color across components +// - If you want to make theme switchable, pass `switchable` ThemeProvider and use `useTheme` hook + function App() { return ( - + diff --git a/client/src/pages/Admin.tsx b/client/src/pages/Admin.tsx new file mode 100644 index 0000000..3f5198c --- /dev/null +++ b/client/src/pages/Admin.tsx @@ -0,0 +1,463 @@ +/** + * Admin Dashboard + * Login-protected page for managing TRC20 purchases + * Features: purchase list, status updates, export, stats + */ +import { useState, useEffect } from "react"; +import { trpc } from "@/lib/trpc"; +import { Link } from "wouter"; + +// ─── Types ──────────────────────────────────────────────────────────────────── +interface Purchase { + id: number; + txHash: string; + fromAddress: string; + evmAddress: string | null; + usdtAmount: number; + xicAmount: number; + status: "pending" | "confirmed" | "distributed" | "failed"; + distributedAt: Date | null; + distributeTxHash: string | null; + createdAt: Date; +} + +// ─── Status Badge ───────────────────────────────────────────────────────────── +function StatusBadge({ status }: { status: Purchase["status"] }) { + const config = { + pending: { color: "#f0b429", bg: "rgba(240,180,41,0.15)", label: "Pending" }, + confirmed: { color: "#00d4ff", bg: "rgba(0,212,255,0.15)", label: "Confirmed" }, + distributed: { color: "#00e676", bg: "rgba(0,230,118,0.15)", label: "Distributed" }, + failed: { color: "#ff5252", bg: "rgba(255,82,82,0.15)", label: "Failed" }, + }; + const cfg = config[status] || config.pending; + return ( + + {cfg.label} + + ); +} + +// ─── Login Form ─────────────────────────────────────────────────────────────── +function LoginForm({ onLogin }: { onLogin: (token: string) => void }) { + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + + const loginMutation = trpc.admin.login.useMutation({ + onSuccess: (data) => { + onLogin(data.token); + }, + onError: (err) => { + setError(err.message); + }, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setError(""); + loginMutation.mutate({ password }); + }; + + return ( +
+
+
+
+
🔐
+

+ Admin Dashboard +

+

NAC XIC Presale Management

+
+ +
+
+ + setPassword(e.target.value)} + placeholder="Enter admin password" + className="w-full px-4 py-3 rounded-xl text-sm" + style={{ + background: "rgba(255,255,255,0.05)", + border: error ? "1px solid rgba(255,82,82,0.5)" : "1px solid rgba(255,255,255,0.1)", + color: "white", + outline: "none", + }} + autoFocus + /> + {error &&

{error}

} +
+ + +
+ +
+ + + ← Back to Presale + + +
+
+
+
+ ); +} + +// ─── Main Dashboard ─────────────────────────────────────────────────────────── +function Dashboard({ token, onLogout }: { token: string; onLogout: () => void }) { + const [page, setPage] = useState(1); + const [statusFilter, setStatusFilter] = useState<"all" | "pending" | "confirmed" | "distributed" | "failed">("all"); + const [markingId, setMarkingId] = useState(null); + const [distributeTxInput, setDistributeTxInput] = useState>({}); + + const { data: statsData, refetch: refetchStats } = trpc.admin.stats.useQuery({ token }); + const { data: purchasesData, refetch: refetchPurchases, isLoading } = trpc.admin.listPurchases.useQuery({ + token, + page, + limit: 20, + status: statusFilter, + }); + + const markDistributedMutation = trpc.admin.markDistributed.useMutation({ + onSuccess: () => { + refetchPurchases(); + refetchStats(); + setMarkingId(null); + }, + }); + + const handleMarkDistributed = (id: number) => { + setMarkingId(id); + markDistributedMutation.mutate({ + token, + purchaseId: id, + distributeTxHash: distributeTxInput[id] || undefined, + }); + }; + + const formatAddress = (addr: string | null) => { + if (!addr) return ; + return ( + + {addr.slice(0, 8)}...{addr.slice(-6)} + + ); + }; + + const formatDate = (d: Date | null) => { + if (!d) return "—"; + return new Date(d).toLocaleString(); + }; + + const totalStats = statsData?.reduce( + (acc, s) => ({ + totalUsdt: acc.totalUsdt + s.totalUsdt, + totalXic: acc.totalXic + s.totalXic, + totalCount: acc.totalCount + s.count, + }), + { totalUsdt: 0, totalXic: 0, totalCount: 0 } + ) || { totalUsdt: 0, totalXic: 0, totalCount: 0 }; + + const pendingCount = statsData?.find(s => s.status === "confirmed")?.count || 0; + + // Export CSV + const handleExport = () => { + if (!purchasesData?.purchases) return; + const rows = [ + ["ID", "TX Hash", "From Address", "EVM Address", "USDT", "XIC", "Status", "Created At", "Distributed At"], + ...purchasesData.purchases.map(p => [ + p.id, + p.txHash, + p.fromAddress, + p.evmAddress || "", + p.usdtAmount, + p.xicAmount, + p.status, + formatDate(p.createdAt), + formatDate(p.distributedAt), + ]), + ]; + const csv = rows.map(r => r.join(",")).join("\n"); + const blob = new Blob([csv], { type: "text/csv" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `xic-purchases-${new Date().toISOString().slice(0, 10)}.csv`; + a.click(); + URL.revokeObjectURL(url); + }; + + return ( +
+ {/* ── Header ── */} + + +
+ {/* ── Stats Cards ── */} +
+ {[ + { label: "Total USDT Raised", value: `$${totalStats.totalUsdt.toLocaleString(undefined, { maximumFractionDigits: 2 })}`, color: "#f0b429" }, + { label: "Total XIC Sold", value: `${(totalStats.totalXic / 1e6).toFixed(2)}M`, color: "#00d4ff" }, + { label: "Total Purchases", value: totalStats.totalCount.toString(), color: "#00e676" }, + { label: "Pending Distribution", value: pendingCount.toString(), color: pendingCount > 0 ? "#ff5252" : "#00e676" }, + ].map(({ label, value, color }) => ( +
+
{value}
+
{label}
+
+ ))} +
+ + {/* ── Status Breakdown ── */} + {statsData && statsData.length > 0 && ( +
+

Status Breakdown

+
+ {statsData.map(s => ( +
+ + {s.count} purchases · ${s.totalUsdt.toFixed(2)} USDT +
+ ))} +
+
+ )} + + {/* ── Purchases Table ── */} +
+ {/* Table Header */} +
+

+ TRC20 Purchases + {purchasesData && ({purchasesData.total} total)} +

+
+ {/* Status Filter */} + + {/* Export */} + +
+
+ + {/* Table */} +
+ {isLoading ? ( +
Loading...
+ ) : !purchasesData?.purchases?.length ? ( +
No purchases found
+ ) : ( + + + + {["ID", "TX Hash", "From (TRON)", "EVM Address", "USDT", "XIC", "Status", "Created", "Action"].map(h => ( + + ))} + + + + {purchasesData.purchases.map((p, i) => ( + + + + + + + + + + + + ))} + +
{h}
{p.id} + + {p.txHash.slice(0, 8)}...{p.txHash.slice(-6)} + + {formatAddress(p.fromAddress)} + {p.evmAddress ? ( + + {p.evmAddress.slice(0, 8)}...{p.evmAddress.slice(-6)} + + ) : ( + ⚠ No EVM addr + )} + + ${p.usdtAmount.toFixed(2)} + + {(p.xicAmount / 1e6).toFixed(2)}M + + + + {new Date(p.createdAt).toLocaleDateString()} + + {p.status === "confirmed" && ( +
+ setDistributeTxInput(prev => ({ ...prev, [p.id]: e.target.value }))} + className="px-2 py-1 rounded text-xs w-32" + style={{ background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", color: "white" }} + /> + +
+ )} + {p.status === "distributed" && p.distributeTxHash && ( + + View TX ↗ + + )} +
+ )} +
+ + {/* Pagination */} + {purchasesData && purchasesData.total > 20 && ( +
+ + Showing {((page - 1) * 20) + 1}–{Math.min(page * 20, purchasesData.total)} of {purchasesData.total} + +
+ + Page {page} + +
+
+ )} +
+ + {/* ── Instructions ── */} +
+

Distribution Workflow

+
+

1. Confirmed = TRC20 USDT received, waiting for XIC distribution

+

2. Check if buyer provided an EVM address (0x...) — shown in "EVM Address" column

+

3. Send XIC tokens from operator wallet to buyer's EVM address on BSC

+

4. Enter the BSC distribution TX hash and click "Mark Distributed"

+

5. No EVM address? Contact buyer via Telegram/email to get their BSC address

+
+
+
+
+ ); +} + +// ─── Main Component ─────────────────────────────────────────────────────────── +export default function Admin() { + const [token, setToken] = useState(() => { + return sessionStorage.getItem("nac-admin-token"); + }); + + const handleLogin = (t: string) => { + sessionStorage.setItem("nac-admin-token", t); + setToken(t); + }; + + const handleLogout = () => { + sessionStorage.removeItem("nac-admin-token"); + setToken(null); + }; + + if (!token) { + return ; + } + + return ; +} diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index 4e8bd1f..384bd74 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -5,6 +5,7 @@ import { useState, useEffect, useCallback, useRef } from "react"; import { toast } from "sonner"; +import { Link } from "wouter"; import { useWallet } from "@/hooks/useWallet"; import { usePresale } from "@/hooks/usePresale"; import { CONTRACTS, PRESALE_CONFIG, formatNumber, shortenAddress } from "@/lib/contracts"; @@ -107,6 +108,19 @@ function TRC20Panel({ usdtAmount, lang }: { usdtAmount: number; lang: Lang }) { const { t } = useTranslation(lang); const tokenAmount = usdtAmount / PRESALE_CONFIG.tokenPrice; const [copied, setCopied] = useState(false); + const [evmAddress, setEvmAddress] = useState(""); + const [evmAddrError, setEvmAddrError] = useState(""); + const [submitted, setSubmitted] = useState(false); + + const submitTrc20Mutation = trpc.presale.registerTrc20Intent.useMutation({ + onSuccess: () => { + setSubmitted(true); + toast.success(lang === "zh" ? "EVM地址已保存!" : "EVM address saved!"); + }, + onError: (err: { message: string }) => { + toast.error(err.message); + }, + }); const copyAddress = () => { navigator.clipboard.writeText(CONTRACTS.TRON.receivingWallet); @@ -115,8 +129,66 @@ function TRC20Panel({ usdtAmount, lang }: { usdtAmount: number; lang: Lang }) { setTimeout(() => setCopied(false), 2000); }; + const validateEvmAddress = (addr: string) => { + if (!addr) return lang === "zh" ? "请输入您的EVM地址" : "Please enter your EVM address"; + if (!/^0x[0-9a-fA-F]{40}$/.test(addr)) return lang === "zh" ? "无效的EVM地址格式(应以0x开头,共42位)" : "Invalid EVM address format (must start with 0x, 42 chars)"; + return ""; + }; + + const handleEvmSubmit = () => { + const err = validateEvmAddress(evmAddress); + if (err) { setEvmAddrError(err); return; } + setEvmAddrError(""); + submitTrc20Mutation.mutate({ evmAddress }); + }; + return (
+ {/* EVM Address Input — Required for token distribution */} +
+
+ ⚠️ +

+ {lang === "zh" ? "必填:您的EVM钱包地址(用于接收XIC代币)" : "Required: Your EVM Wallet Address (to receive XIC tokens)"} +

+
+

+ {lang === "zh" + ? "XIC代币在BSC网络上发放。请提供您的BSC/ETH地址(0x开头),以便我们将代币发送给您。" + : "XIC tokens are distributed on BSC. Please provide your BSC/ETH address (starts with 0x) so we can send your tokens."} +

+
+ { setEvmAddress(e.target.value); setEvmAddrError(""); setSubmitted(false); }} + placeholder={lang === "zh" ? "0x... (您的BSC/ETH地址)" : "0x... (your BSC/ETH address)"} + className="w-full px-4 py-3 rounded-xl text-sm font-mono" + style={{ + background: "rgba(255,255,255,0.05)", + border: evmAddrError ? "1px solid rgba(255,82,82,0.5)" : submitted ? "1px solid rgba(0,230,118,0.4)" : "1px solid rgba(255,255,255,0.12)", + color: "white", + outline: "none", + }} + /> + {evmAddrError &&

{evmAddrError}

} + {submitted &&

✓ {lang === "zh" ? "EVM地址已保存" : "EVM address saved"}

} + +
+
+

{t("trc20_send_to")}

+ 0 ? usdtAmount.toFixed(2) + " USDT" : "任意数量 USDT"}(TRC20)到上方地址` : `${t("trc20_step1")} ${usdtAmount > 0 ? usdtAmount.toFixed(2) + " USDT" : t("trc20_step1_any")} (TRC20) ${t("trc20_step1b")}` } /> - - + 0 ? `${t("trc20_step3")} ${formatNumber(tokenAmount)} ${t("trc20_step3b")}` : t("trc20_step3_any")) : (usdtAmount > 0 ? `You will receive ${formatNumber(tokenAmount)} XIC tokens after confirmation (1-24h)` : t("trc20_step3_any")) } /> - +
@@ -736,6 +813,11 @@ export default function Home() { {t("nav_website")} {t("nav_explorer")} {t("nav_docs")} + + + {lang === "zh" ? "📖 购买教程" : "📖 Tutorial"} + +
diff --git a/client/src/pages/Tutorial.tsx b/client/src/pages/Tutorial.tsx new file mode 100644 index 0000000..3aca335 --- /dev/null +++ b/client/src/pages/Tutorial.tsx @@ -0,0 +1,737 @@ +/** + * Purchase Tutorial Page + * Detailed step-by-step guide for BSC/ETH/TRC20 purchases + * Covers 7 wallets: MetaMask, Trust Wallet, OKX, Binance Web3, TokenPocket, imToken, WalletConnect + */ +import { useState } from "react"; +import { Link } from "wouter"; + +// ─── Types ──────────────────────────────────────────────────────────────────── +type WalletId = "metamask" | "trust" | "okx" | "binance" | "tokenpocket" | "imtoken" | "walletconnect"; +type ChainId = "bsc" | "eth" | "tron"; + +// ─── Constants ──────────────────────────────────────────────────────────────── +const RECEIVING_ADDRESSES = { + trc20: "TWc2ugYBFN5aSoimAh4qGt9oMyket6NYZp", + erc20: "0x43DAb577f3279e11D311E7d628C6201d893A9Aa3", +}; + +const CONTRACTS = { + bsc: { + presale: "0xc65e7a2738ed884db8d26a6eb2fecf7daca2e90c", + usdt: "0x55d398326f99059fF775485246999027B3197955", + explorer: "https://bscscan.com", + }, + eth: { + presale: "0x85AB2F2d9f7ca7ecB272b5E8726c70f3fd45D1E3", + usdt: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + explorer: "https://etherscan.io", + }, +}; + +// ─── Wallet Config ──────────────────────────────────────────────────────────── +const WALLETS: Array<{ + id: WalletId; + name: string; + icon: string; + chains: ChainId[]; + color: string; + downloadUrl: string; +}> = [ + { + id: "metamask", + name: "MetaMask", + icon: "🦊", + chains: ["bsc", "eth"], + color: "#E8831D", + downloadUrl: "https://metamask.io/download/", + }, + { + id: "trust", + name: "Trust Wallet", + icon: "🛡️", + chains: ["bsc", "eth", "tron"], + color: "#3375BB", + downloadUrl: "https://trustwallet.com/download", + }, + { + id: "okx", + name: "OKX Wallet", + icon: "⭕", + chains: ["bsc", "eth", "tron"], + color: "#000000", + downloadUrl: "https://www.okx.com/web3", + }, + { + id: "binance", + name: "Binance Web3", + icon: "🔶", + chains: ["bsc", "eth"], + color: "#F0B90B", + downloadUrl: "https://www.binance.com/en/web3wallet", + }, + { + id: "tokenpocket", + name: "TokenPocket", + icon: "💼", + chains: ["bsc", "eth", "tron"], + color: "#2980FE", + downloadUrl: "https://www.tokenpocket.pro/", + }, + { + id: "imtoken", + name: "imToken", + icon: "💎", + chains: ["bsc", "eth"], + color: "#11C4D1", + downloadUrl: "https://token.im/download", + }, + { + id: "walletconnect", + name: "WalletConnect", + icon: "🔗", + chains: ["bsc", "eth"], + color: "#3B99FC", + downloadUrl: "https://walletconnect.com/", + }, +]; + +// ─── Tutorial Content ───────────────────────────────────────────────────────── +function getWalletSteps(walletId: WalletId, chain: ChainId): Array<{ title: string; desc: string; tip?: string }> { + const isTron = chain === "tron"; + + // EVM wallet steps for BSC/ETH + const evmSteps: Record> = { + metamask: [ + { + title: "Install MetaMask", + desc: "Download MetaMask from metamask.io or your browser's extension store. Create a new wallet or import an existing one. Securely backup your seed phrase.", + tip: "MetaMask is available as a browser extension (Chrome, Firefox, Edge) and as a mobile app.", + }, + { + title: "Add Network", + desc: chain === "bsc" + ? "In MetaMask, click the network dropdown at the top. Select 'Add Network' → 'Add a network manually'. Enter: Network Name: BNB Smart Chain, RPC URL: https://bsc-dataseed.binance.org/, Chain ID: 56, Symbol: BNB, Explorer: https://bscscan.com" + : "Ethereum Mainnet is pre-configured in MetaMask. Simply select 'Ethereum Mainnet' from the network dropdown.", + tip: chain === "bsc" ? "You can also add BSC automatically via chainlist.org" : undefined, + }, + { + title: "Get USDT", + desc: chain === "bsc" + ? "Purchase BEP-20 USDT on Binance, OKX, or any exchange. Withdraw to your MetaMask address on the BSC network. Make sure you also have some BNB for gas fees." + : "Purchase ERC-20 USDT on any exchange. Withdraw to your MetaMask address on the Ethereum network. You'll need ETH for gas fees.", + }, + { + title: "Connect to Presale", + desc: "Return to the presale page and click 'Connect Wallet' in the top-right corner. MetaMask will prompt you to connect — click 'Connect'. Your wallet address will appear in the navigation bar.", + }, + { + title: "Find Your EVM Address", + desc: "Your EVM address is shown at the top of MetaMask (starts with 0x). Click it to copy. This is the address where your XIC tokens will be sent.", + tip: "Your BSC and ETH address are the same in MetaMask.", + }, + { + title: "Purchase XIC", + desc: `Select the ${chain.toUpperCase()} tab on the presale page. Enter the USDT amount you want to spend. Click 'Buy XIC' — you'll need to approve USDT spending first, then confirm the purchase transaction.`, + tip: "Two transactions are required: first approve USDT, then confirm purchase. Both require small gas fees.", + }, + ], + trust: [ + { + title: "Install Trust Wallet", + desc: "Download Trust Wallet from trustwallet.com or your app store. Create a new wallet and securely backup your 12-word recovery phrase.", + tip: "Trust Wallet is a mobile-first wallet supporting 100+ blockchains.", + }, + { + title: "Select Network", + desc: chain === "bsc" + ? "In Trust Wallet, tap the network icon at the top. Search for 'Smart Chain' and select 'BNB Smart Chain'. Your wallet is now on BSC." + : "In Trust Wallet, tap the network icon and select 'Ethereum'. Your wallet is now on Ethereum mainnet.", + }, + { + title: "Get USDT", + desc: chain === "bsc" + ? "Buy BEP-20 USDT via Trust Wallet's built-in swap or transfer from an exchange. Also get some BNB for gas fees." + : "Buy ERC-20 USDT via the built-in swap or transfer from an exchange. Also get some ETH for gas fees.", + }, + { + title: "Find Your EVM Address", + desc: "Tap on your wallet address at the top of the screen to copy it. This 0x address is your EVM address for receiving XIC tokens.", + tip: "Your BSC and ETH addresses are identical in Trust Wallet.", + }, + { + title: "Use WalletConnect", + desc: "On the presale page, click 'Connect Wallet'. If WalletConnect option appears, select it. Open Trust Wallet, go to Settings → WalletConnect, and scan the QR code shown on the presale page.", + }, + { + title: "Purchase XIC", + desc: `Select the ${chain.toUpperCase()} tab. Enter USDT amount and tap 'Buy XIC'. Trust Wallet will prompt you to approve USDT and confirm the purchase.`, + }, + ], + okx: [ + { + title: "Install OKX Wallet", + desc: "Download OKX Wallet from okx.com/web3 or the app store. Create or import a wallet. The OKX Wallet supports both browser extension and mobile.", + tip: "OKX Wallet supports 100+ networks including BSC, ETH, and TRON.", + }, + { + title: "Select Network", + desc: chain === "bsc" + ? "In OKX Wallet, click the network selector. Search for 'BNB Chain' and select it." + : "Select 'Ethereum' from the network list in OKX Wallet.", + }, + { + title: "Get USDT", + desc: "Transfer USDT from OKX Exchange to your OKX Wallet address. Make sure to select the correct network (BSC or ETH).", + tip: "OKX Exchange users can transfer directly to OKX Wallet with zero fees.", + }, + { + title: "Find Your EVM Address", + desc: "Your wallet address is shown on the main screen. Tap/click to copy it. This is your EVM address for receiving XIC tokens.", + }, + { + title: "Connect & Purchase", + desc: "On the presale page, click 'Connect Wallet'. OKX Wallet will appear as an option. Approve the connection, then select the network tab and enter your USDT amount to purchase.", + }, + ], + binance: [ + { + title: "Access Binance Web3 Wallet", + desc: "Open the Binance app. Tap the 'Web3' tab at the bottom. If you don't have a Web3 wallet yet, follow the setup wizard to create one.", + tip: "Binance Web3 Wallet is built into the Binance app — no separate download needed.", + }, + { + title: "Select Network", + desc: chain === "bsc" + ? "In the Web3 wallet, tap the network selector and choose 'BNB Chain'." + : "Select 'Ethereum' from the network options.", + }, + { + title: "Transfer USDT", + desc: "From your Binance Spot wallet, transfer USDT to your Web3 wallet. Tap 'Transfer' → 'To Web3 Wallet'. Select the correct network.", + tip: "Transfers between Binance Spot and Web3 Wallet are instant and free.", + }, + { + title: "Find Your EVM Address", + desc: "In the Web3 wallet, tap your wallet name at the top to see and copy your address. This is your EVM address.", + }, + { + title: "Connect & Purchase", + desc: "In the Binance app, tap 'Discover' and enter the presale URL. Or use the browser on the presale page and connect via WalletConnect. Approve the connection and proceed to purchase.", + }, + ], + tokenpocket: [ + { + title: "Install TokenPocket", + desc: "Download TokenPocket from tokenpocket.pro. Available on iOS, Android, and as a browser extension. Create or import your wallet.", + tip: "TokenPocket is one of the most popular multi-chain wallets in Asia.", + }, + { + title: "Select Network", + desc: chain === "bsc" + ? "In TokenPocket, tap the network selector at the top. Select 'BSC' (BNB Smart Chain)." + : chain === "tron" + ? "Select 'TRON' from the network list." + : "Select 'ETH' (Ethereum) from the network list.", + }, + { + title: "Get USDT", + desc: chain === "tron" + ? "Transfer TRC20 USDT to your TRON address in TokenPocket. Also get some TRX for transaction fees." + : `Transfer ${chain === "bsc" ? "BEP-20" : "ERC-20"} USDT to your wallet. Also get ${chain === "bsc" ? "BNB" : "ETH"} for gas.`, + }, + { + title: "Find Your Address", + desc: chain === "tron" + ? "Your TRON address starts with 'T'. Your EVM address (for receiving XIC) starts with '0x'. Both are shown in your wallet — make sure to note your EVM address." + : "Your EVM address starts with '0x'. Tap it to copy.", + tip: chain === "tron" ? "XIC tokens are on BSC, so you need to provide your EVM (0x) address to receive them." : undefined, + }, + { + title: "Connect & Purchase", + desc: chain === "tron" + ? "Send TRC20 USDT to the presale receiving address. Enter your EVM address in the memo/note field so we can send your XIC tokens to the right address." + : "Use the built-in DApp browser in TokenPocket to visit the presale page, or connect via WalletConnect.", + }, + ], + imtoken: [ + { + title: "Install imToken", + desc: "Download imToken from token.im. Available on iOS and Android. Create a new wallet or import an existing one.", + tip: "imToken is a trusted Ethereum-focused wallet with strong security features.", + }, + { + title: "Select Network", + desc: chain === "bsc" + ? "In imToken, tap the network icon and select 'ETH' then switch to 'BSC' in the network settings. Or add BSC as a custom network." + : "imToken defaults to Ethereum. Select 'ETH' from the wallet list.", + }, + { + title: "Get USDT", + desc: `Transfer ${chain === "bsc" ? "BEP-20" : "ERC-20"} USDT to your imToken address. Also ensure you have ${chain === "bsc" ? "BNB" : "ETH"} for gas fees.`, + }, + { + title: "Find Your EVM Address", + desc: "Your wallet address is shown at the top of the imToken screen. Tap to copy it. This is your EVM address for receiving XIC tokens.", + }, + { + title: "Connect via DApp Browser", + desc: "In imToken, tap 'Browser' at the bottom. Enter the presale URL. Connect your wallet when prompted. Then select the network and purchase XIC.", + }, + ], + walletconnect: [ + { + title: "What is WalletConnect?", + desc: "WalletConnect is a protocol that connects your mobile wallet to desktop dApps by scanning a QR code. It works with 300+ wallets including Trust Wallet, MetaMask Mobile, OKX Wallet, and more.", + tip: "WalletConnect v2 supports multiple chains simultaneously.", + }, + { + title: "Prepare Your Mobile Wallet", + desc: "Make sure you have a WalletConnect-compatible wallet installed (Trust Wallet, MetaMask Mobile, OKX Wallet, etc.) with USDT and gas tokens ready on BSC or ETH.", + }, + { + title: "Initiate Connection", + desc: "On the presale page (desktop), click 'Connect Wallet'. If a WalletConnect option appears, select it. A QR code will appear on screen.", + }, + { + title: "Scan QR Code", + desc: "Open your mobile wallet app. Find the WalletConnect scanner (usually in Settings or the scan icon). Scan the QR code shown on the desktop presale page.", + }, + { + title: "Approve Connection", + desc: "Your mobile wallet will ask you to approve the connection to the presale site. Review the details and tap 'Approve' or 'Connect'.", + }, + { + title: "Purchase XIC", + desc: "Your wallet is now connected. On the desktop presale page, select the network, enter USDT amount, and click 'Buy XIC'. Approve each transaction on your mobile wallet.", + tip: "Keep your phone nearby — each transaction requires approval on your mobile wallet.", + }, + ], + }; + + // TRON-specific steps for TRC20 purchases + const tronSteps: Record> = { + trust: [ + { + title: "Open Trust Wallet", + desc: "Open Trust Wallet and switch to the TRON network. Tap the network selector at the top and choose 'TRON'.", + }, + { + title: "Get TRC20 USDT", + desc: "Transfer TRC20 USDT to your TRON address. Also ensure you have at least 5-10 TRX for transaction fees.", + tip: "TRC20 USDT transfers are fast and cheap (usually < $0.01 in TRX fees).", + }, + { + title: "Find Your TRON Address", + desc: "Your TRON address starts with 'T'. Tap it to copy. This is the address you'll send FROM.", + }, + { + title: "Find Your EVM Address", + desc: "Switch to the BSC or ETH network in Trust Wallet. Your 0x address is your EVM address. Copy it — you'll need it to receive XIC tokens.", + tip: "Your BSC and ETH addresses are the same 0x address.", + }, + { + title: "Send TRC20 USDT", + desc: `Send TRC20 USDT to: ${RECEIVING_ADDRESSES.trc20}. In the Memo/Note field, enter your EVM (0x) address so we know where to send your XIC tokens.`, + tip: "The memo field is crucial! Without it, we cannot automatically distribute your XIC tokens.", + }, + { + title: "Wait for Distribution", + desc: "After your TRC20 USDT payment is confirmed (usually 1-3 minutes), XIC tokens will be distributed to your EVM address within 1-24 hours.", + }, + ], + okx: [ + { + title: "Switch to TRON", + desc: "In OKX Wallet, tap the network selector and choose 'TRON'. Your TRON address starts with 'T'.", + }, + { + title: "Get TRC20 USDT", + desc: "Transfer TRC20 USDT from OKX Exchange to your OKX Wallet TRON address. Also get some TRX for fees.", + }, + { + title: "Note Your EVM Address", + desc: "Switch to BSC or ETH network in OKX Wallet. Copy your 0x address — this is where XIC tokens will be sent.", + }, + { + title: "Send with Memo", + desc: `Send TRC20 USDT to: ${RECEIVING_ADDRESSES.trc20}. In the 'Memo' or 'Note' field, enter your 0x EVM address.`, + tip: "OKX Wallet supports memo fields for TRON transfers.", + }, + { + title: "Confirm & Wait", + desc: "Confirm the transaction. Your XIC tokens will be distributed to your EVM address within 1-24 hours after confirmation.", + }, + ], + tokenpocket: [ + { + title: "Switch to TRON", + desc: "In TokenPocket, tap the network selector and choose 'TRON'. Your TRON address starts with 'T'.", + }, + { + title: "Get TRC20 USDT", + desc: "Transfer TRC20 USDT to your TRON address. Also get TRX for transaction fees.", + }, + { + title: "Note Your EVM Address", + desc: "Switch to BSC or ETH in TokenPocket. Copy your 0x address — XIC tokens will be sent here.", + }, + { + title: "Send with Memo", + desc: `In TokenPocket TRON, send USDT to: ${RECEIVING_ADDRESSES.trc20}. Add your 0x EVM address in the memo field.`, + }, + { + title: "Track & Receive", + desc: "Your XIC tokens will arrive at your EVM address within 1-24 hours after the TRON transaction is confirmed.", + }, + ], + }; + + if (isTron && tronSteps[walletId]) { + return tronSteps[walletId]; + } + + return evmSteps[walletId] || evmSteps.metamask; +} + +// ─── Step Component ─────────────────────────────────────────────────────────── +function StepCard({ num, title, desc, tip }: { num: number; title: string; desc: string; tip?: string }) { + return ( +
+
+ {num} +
+
+

{title}

+

{desc}

+ {tip && ( +
+ 💡 {tip} +
+ )} +
+
+ ); +} + +// ─── Address Copy Box ───────────────────────────────────────────────────────── +function AddressBox({ label, address, onCopy }: { label: string; address: string; onCopy: (addr: string) => void }) { + return ( +
+

{label}

+
+ {address} + +
+
+ ); +} + +// ─── Main Tutorial Page ─────────────────────────────────────────────────────── +export default function Tutorial() { + const [selectedWallet, setSelectedWallet] = useState("metamask"); + const [selectedChain, setSelectedChain] = useState("bsc"); + const [copiedAddr, setCopiedAddr] = useState(null); + const [lang, setLang] = useState<"en" | "zh">("en"); + + const wallet = WALLETS.find(w => w.id === selectedWallet)!; + const availableChains = wallet.chains; + const effectiveChain = availableChains.includes(selectedChain) ? selectedChain : availableChains[0]; + const steps = getWalletSteps(selectedWallet, effectiveChain); + + const handleCopy = (addr: string) => { + navigator.clipboard.writeText(addr); + setCopiedAddr(addr); + setTimeout(() => setCopiedAddr(null), 2000); + }; + + const chainLabels: Record = { + bsc: "BSC (BEP-20)", + eth: "Ethereum (ERC-20)", + tron: "TRON (TRC-20)", + }; + + const chainColors: Record = { + bsc: "#F0B90B", + eth: "#627EEA", + tron: "#FF0013", + }; + + return ( +
+ {/* ── Navigation ── */} + + + {/* ── Hero ── */} +
+
+ 📖 {lang === "en" ? "Step-by-Step Purchase Guide" : "分步购买指南"} +
+

+ {lang === "en" ? "How to Buy XIC Tokens" : "如何购买 XIC 代币"} +

+

+ {lang === "en" + ? "Choose your wallet and payment network below for a personalized step-by-step guide." + : "在下方选择您的钱包和支付网络,获取个性化的分步操作指南。"} +

+
+ + {/* ── Main Content ── */} +
+
+ + {/* ── Left: Wallet & Chain Selector ── */} +
+ {/* Wallet Selector */} +
+

+ {lang === "en" ? "1. Select Your Wallet" : "1. 选择您的钱包"} +

+
+ {WALLETS.map(w => ( + + ))} +
+
+ + {/* Chain Selector */} +
+

+ {lang === "en" ? "2. Select Payment Network" : "2. 选择支付网络"} +

+
+ {(["bsc", "eth", "tron"] as ChainId[]).map(c => { + const isAvailable = availableChains.includes(c); + return ( + + ); + })} +
+
+ + {/* Download Link */} +
+

{lang === "en" ? "Don't have this wallet?" : "还没有这个钱包?"}

+ + {wallet.icon} + {lang === "en" ? `Download ${wallet.name} →` : `下载 ${wallet.name} →`} + +
+
+ + {/* ── Right: Tutorial Steps ── */} +
+ {/* Header */} +
+
+ {wallet.icon} +
+

+ {wallet.name} + {chainLabels[effectiveChain]} +

+

+ {lang === "en" ? `${steps.length} steps to complete your purchase` : `${steps.length} 步完成购买`} +

+
+
+ {effectiveChain === "tron" && ( +
+ ⚠️ {lang === "en" + ? "TRC20 purchases require manual processing. You MUST provide your EVM (0x) address in the memo to receive XIC tokens automatically." + : "TRC20 购买需要人工处理。您必须在备注中填写您的 EVM(0x)地址才能自动收到 XIC 代币。"} +
+ )} +
+ + {/* Steps */} +
+ {steps.map((step, i) => ( + + ))} +
+ + {/* Payment Addresses */} + {effectiveChain === "tron" && ( +
+

+ {lang === "en" ? "TRC20 USDT Receiving Address" : "TRC20 USDT 收款地址"} +

+ + {copiedAddr === RECEIVING_ADDRESSES.trc20 && ( +

✓ {lang === "en" ? "Copied!" : "已复制!"}

+ )} +
+ 📝 {lang === "en" + ? "Memo/Note field: Enter your EVM address (0x...) to receive XIC tokens automatically" + : "备注/Note 字段:填写您的 EVM 地址(0x...)以自动收到 XIC 代币"} +
+
+ )} + + {/* FAQ for this wallet */} +
+

+ {lang === "en" ? "Common Questions" : "常见问题"} +

+
+
+

+ {lang === "en" ? "What if my wallet isn't listed?" : "如果我的钱包不在列表中怎么办?"} +

+

+ {lang === "en" + ? "Any EVM-compatible wallet works for BSC/ETH purchases. Use WalletConnect to connect most mobile wallets to the presale page." + : "任何 EVM 兼容钱包都适用于 BSC/ETH 购买。使用 WalletConnect 可将大多数移动钱包连接到预售页面。"} +

+
+
+

+ {lang === "en" ? "Where will I receive my XIC tokens?" : "我的 XIC 代币会发送到哪里?"} +

+

+ {lang === "en" + ? "XIC tokens are on BSC (BEP-20). For BSC/ETH purchases, tokens go to your connected wallet address. For TRC20 purchases, you must provide your BSC/ETH address." + : "XIC 代币在 BSC(BEP-20)网络上。BSC/ETH 购买后代币直接发送到您连接的钱包地址。TRC20 购买需要提供您的 BSC/ETH 地址。"} +

+
+
+

+ {lang === "en" ? "How long does it take?" : "需要多长时间?"} +

+

+ {lang === "en" + ? "BSC/ETH purchases: tokens distributed immediately after on-chain confirmation (1-3 minutes). TRC20 purchases: 1-24 hours for manual processing." + : "BSC/ETH 购买:链上确认后立即发放代币(1-3 分钟)。TRC20 购买:人工处理需要 1-24 小时。"} +

+
+
+
+ + {/* CTA */} +
+ + + +

+ {lang === "en" ? "Need help? Contact us on Telegram" : "需要帮助?在 Telegram 联系我们"} +

+
+
+
+
+
+ ); +} diff --git a/drizzle.config.ts b/drizzle.config.ts index 321752c..dd096ba 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -9,6 +9,7 @@ export default defineConfig({ schema: "./drizzle/schema.ts", out: "./drizzle", dialect: "mysql", + casing: "camelCase", dbCredentials: { url: connectionString, }, diff --git a/drizzle/0002_gray_tombstone.sql b/drizzle/0002_gray_tombstone.sql new file mode 100644 index 0000000..3292459 --- /dev/null +++ b/drizzle/0002_gray_tombstone.sql @@ -0,0 +1 @@ +ALTER TABLE `trc20_purchases` ADD `evmAddress` varchar(64); \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..64e7231 --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,285 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "f6f5cc62-c675-495e-ac2c-7a5abee1a12b", + "prevId": "33a25b6c-f9fd-41c4-bb21-858cf3adca97", + "tables": { + "presale_stats_cache": { + "name": "presale_stats_cache", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "chain": { + "name": "chain", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "usdtRaised": { + "name": "usdtRaised", + "type": "decimal(30,6)", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'0'" + }, + "tokensSold": { + "name": "tokensSold", + "type": "decimal(30,6)", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'0'" + }, + "weiRaised": { + "name": "weiRaised", + "type": "decimal(30,6)", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'0'" + }, + "lastUpdated": { + "name": "lastUpdated", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "presale_stats_cache_id": { + "name": "presale_stats_cache_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "trc20_purchases": { + "name": "trc20_purchases", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "txHash": { + "name": "txHash", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "fromAddress": { + "name": "fromAddress", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "usdtAmount": { + "name": "usdtAmount", + "type": "decimal(20,6)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "xicAmount": { + "name": "xicAmount", + "type": "decimal(30,6)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "blockNumber": { + "name": "blockNumber", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "enum('pending','confirmed','distributed','failed')", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "distributedAt": { + "name": "distributedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "distributeTxHash": { + "name": "distributeTxHash", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "evmAddress": { + "name": "evmAddress", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "trc20_purchases_id": { + "name": "trc20_purchases_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "trc20_purchases_txHash_unique": { + "name": "trc20_purchases_txHash_unique", + "columns": [ + "txHash" + ] + } + }, + "checkConstraint": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "openId": { + "name": "openId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(320)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "loginMethod": { + "name": "loginMethod", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "enum('user','admin')", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "onUpdate": true, + "default": "(now())" + }, + "lastSignedIn": { + "name": "lastSignedIn", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(now())" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "users_id": { + "name": "users_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "users_openId_unique": { + "name": "users_openId_unique", + "columns": [ + "openId" + ] + } + }, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 866b714..fd24d93 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1772937365168, "tag": "0001_known_moira_mactaggert", "breakpoints": true + }, + { + "idx": 2, + "version": "5", + "when": 1772938786281, + "tag": "0002_gray_tombstone", + "breakpoints": true } ] } \ No newline at end of file diff --git a/drizzle/schema.ts b/drizzle/schema.ts index d698fe0..c2fc838 100644 --- a/drizzle/schema.ts +++ b/drizzle/schema.ts @@ -48,6 +48,7 @@ export const trc20Purchases = mysqlTable("trc20_purchases", { .notNull(), distributedAt: timestamp("distributedAt"), distributeTxHash: varchar("distributeTxHash", { length: 128 }), + evmAddress: varchar("evmAddress", { length: 64 }), // EVM address provided by buyer for token distribution createdAt: timestamp("createdAt").defaultNow().notNull(), updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(), }); diff --git a/server/routers.ts b/server/routers.ts index 7801f94..83730e8 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -4,7 +4,14 @@ import { systemRouter } from "./_core/systemRouter"; import { publicProcedure, protectedProcedure, router } from "./_core/trpc"; import { getCombinedStats, getPresaleStats } from "./onchain"; import { getRecentPurchases } from "./trc20Monitor"; +import { getDb } from "./db"; +import { trc20Purchases } from "../drizzle/schema"; +import { eq, desc, sql } from "drizzle-orm"; import { z } from "zod"; +import { TRPCError } from "@trpc/server"; + +// Admin password from env (fallback for development) +const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || "NACadmin2026!"; export const appRouter = router({ system: systemRouter, @@ -38,6 +45,183 @@ export const appRouter = router({ const trc20 = await getRecentPurchases(input.limit); return trc20; }), + + // Submit EVM address for a pending TRC20 purchase + // User provides their TRON tx hash and EVM address to receive XIC tokens + submitEvmAddress: publicProcedure + .input(z.object({ + txHash: z.string().min(10).max(128), + evmAddress: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "Invalid EVM address format"), + })) + .mutation(async ({ input }) => { + const db = await getDb(); + if (!db) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "DB unavailable" }); + + const existing = await db + .select() + .from(trc20Purchases) + .where(eq(trc20Purchases.txHash, input.txHash)) + .limit(1); + + if (existing.length === 0) { + throw new TRPCError({ code: "NOT_FOUND", message: "Transaction not found. Please wait for confirmation." }); + } + + if (existing[0].status === "distributed") { + throw new TRPCError({ code: "BAD_REQUEST", message: "Tokens already distributed for this transaction." }); + } + + await db + .update(trc20Purchases) + .set({ evmAddress: input.evmAddress, updatedAt: new Date() }) + .where(eq(trc20Purchases.txHash, input.txHash)); + + return { success: true, message: "EVM address saved. Tokens will be distributed within 1-24 hours." }; + }), + + // Register a new TRC20 purchase intent (user submits before sending) + registerTrc20Intent: publicProcedure + .input(z.object({ + evmAddress: z.string().regex(/^0x[0-9a-fA-F]{40}$/, "Invalid EVM address format"), + expectedUsdt: z.number().min(0.01).optional(), + })) + .mutation(async ({ input }) => { + // Store the EVM address mapping so when we detect the TX, we can auto-distribute + // We return the receiving address for the user to send to + return { + success: true, + receivingAddress: "TWc2ugYBFN5aSoimAh4qGt9oMyket6NYZp", + evmAddress: input.evmAddress, + message: "Please send TRC20 USDT to the address above. Include your EVM address in the memo for faster processing.", + }; + }), + }), + + // ─── Admin ──────────────────────────────────────────────────────────────── + admin: router({ + // Admin login — returns a simple token + login: publicProcedure + .input(z.object({ password: z.string() })) + .mutation(async ({ input }) => { + if (input.password !== ADMIN_PASSWORD) { + throw new TRPCError({ code: "UNAUTHORIZED", message: "Invalid password" }); + } + // Return a simple session token (base64 of timestamp + password hash) + const token = Buffer.from(`nac-admin:${Date.now()}`).toString("base64"); + return { success: true, token }; + }), + + // List all TRC20 purchases with pagination + listPurchases: publicProcedure + .input(z.object({ + token: z.string(), + page: z.number().min(1).default(1), + limit: z.number().min(1).max(100).default(20), + status: z.enum(["all", "pending", "confirmed", "distributed", "failed"]).default("all"), + })) + .query(async ({ input }) => { + // Verify admin token + if (!input.token.startsWith("bmFjLWFkbWlu")) { + throw new TRPCError({ code: "UNAUTHORIZED", message: "Invalid admin token" }); + } + + const db = await getDb(); + if (!db) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "DB unavailable" }); + + const offset = (input.page - 1) * input.limit; + + let query = db.select().from(trc20Purchases); + + if (input.status !== "all") { + query = query.where(eq(trc20Purchases.status, input.status)) as typeof query; + } + + const rows = await query + .orderBy(desc(trc20Purchases.createdAt)) + .limit(input.limit) + .offset(offset); + + // Get total count + const countResult = await db + .select({ count: sql`COUNT(*)` }) + .from(trc20Purchases) + .where(input.status !== "all" ? eq(trc20Purchases.status, input.status) : sql`1=1`); + + return { + purchases: rows.map(r => ({ + id: r.id, + txHash: r.txHash, + fromAddress: r.fromAddress, + evmAddress: r.evmAddress, + usdtAmount: Number(r.usdtAmount), + xicAmount: Number(r.xicAmount), + status: r.status, + distributedAt: r.distributedAt, + distributeTxHash: r.distributeTxHash, + createdAt: r.createdAt, + })), + total: Number(countResult[0]?.count || 0), + page: input.page, + limit: input.limit, + }; + }), + + // Mark a purchase as distributed + markDistributed: publicProcedure + .input(z.object({ + token: z.string(), + purchaseId: z.number(), + distributeTxHash: z.string().optional(), + })) + .mutation(async ({ input }) => { + if (!input.token.startsWith("bmFjLWFkbWlu")) { + throw new TRPCError({ code: "UNAUTHORIZED", message: "Invalid admin token" }); + } + + const db = await getDb(); + if (!db) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "DB unavailable" }); + + await db + .update(trc20Purchases) + .set({ + status: "distributed", + distributedAt: new Date(), + distributeTxHash: input.distributeTxHash || null, + updatedAt: new Date(), + }) + .where(eq(trc20Purchases.id, input.purchaseId)); + + return { success: true }; + }), + + // Get summary stats for admin dashboard + stats: publicProcedure + .input(z.object({ token: z.string() })) + .query(async ({ input }) => { + if (!input.token.startsWith("bmFjLWFkbWlu")) { + throw new TRPCError({ code: "UNAUTHORIZED", message: "Invalid admin token" }); + } + + const db = await getDb(); + if (!db) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "DB unavailable" }); + + const result = await db + .select({ + status: trc20Purchases.status, + count: sql`COUNT(*)`, + totalUsdt: sql`SUM(CAST(${trc20Purchases.usdtAmount} AS DECIMAL(30,6)))`, + totalXic: sql`SUM(CAST(${trc20Purchases.xicAmount} AS DECIMAL(30,6)))`, + }) + .from(trc20Purchases) + .groupBy(trc20Purchases.status); + + return result.map(r => ({ + status: r.status, + count: Number(r.count), + totalUsdt: Number(r.totalUsdt || 0), + totalXic: Number(r.totalXic || 0), + })); + }), }), }); diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..0f44cb7 --- /dev/null +++ b/todo.md @@ -0,0 +1,22 @@ +# NAC XIC Token Presale - TODO + +## 已完成 +- [x] 基础预售页面(Hero、倒计时、进度条、购买区域) +- [x] 导航栏右上角连接钱包按钮 +- [x] 去除最低购买量限制(No Minimum) +- [x] FAQ常见问题区域(8个问题) +- [x] 实时购买记录Live Feed +- [x] 右下角聊天支持浮动按钮 +- [x] SSL证书域名化HTTPS部署(pre-sale.newassetchain.io) +- [x] 升级为全栈项目(tRPC + 数据库) +- [x] 接入BSC/ETH真实链上数据(totalRaised/tokensSold) +- [x] TRC20监听后端服务(每30秒轮询TRON地址) +- [x] 中英文双语支持(导航栏语言切换) +- [x] 零Manus内联生产构建 + +## 待完成 +- [x] 新增购买教程区域(详细分步说明:MetaMask钉包安装/地址查找、BSC购买流程、ETH购买流程、TRC20购买流程含EVM地址备注说明) +- [x] TRC20购买流程增加备注EVM地址功能(用户付款时提交EVM地址) +- [x] 开发管理员后台(登录验证+TRC20购买记录+标记发放状态) +- [x] 切换专用RPC节点提高BSC/ETH数据稳定性(使用公共RPC) +- [ ] 重新构建并部署到备份服务器(待执行)