Skip to content
Merged

done #36

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions Backend/Workspace/Routes/CreateRoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,18 @@ router.post("/", VCM('LOGIN_TOKEN', process.env.LOGIN_SECRET), upload.fields([{
// ===== DBに保存する前に、モザイクをブロックチェーンに登録 =====
try {
console.log("[CreateRoom] Announcing Mosaic Definition Transaction...");
await SignAndAnnounce(mosaicDefinitionTx, privateKey, facade, 'https://sym-test-01.opening-line.jp:3001');
const definitionResult = await SignAndAnnounce(
mosaicDefinitionTx,
privateKey,
facade,
'https://sym-test-01.opening-line.jp:3001',
{
waitForConfirmation: true,
confirmationTimeoutMs: 180000,
pollIntervalMs: 2000
}
);
console.log("[CreateRoom] Mosaic Definition TX Hash:", definitionResult.hash);
console.log("[CreateRoom] Mosaic Definition TX Announced Successfully!");

// ===== 供給量設定トランザクション作成・送信 =====
Expand All @@ -103,7 +114,18 @@ router.post("/", VCM('LOGIN_TOKEN', process.env.LOGIN_SECRET), upload.fields([{
});

console.log("[CreateRoom] Announcing Supply Change Transaction...");
await SignAndAnnounce(supplyTx, privateKey, supplyFacade, 'https://sym-test-01.opening-line.jp:3001');
const supplyResult = await SignAndAnnounce(
supplyTx,
privateKey,
supplyFacade,
'https://sym-test-01.opening-line.jp:3001',
{
waitForConfirmation: true,
confirmationTimeoutMs: 180000,
pollIntervalMs: 2000
}
);
console.log("[CreateRoom] Supply Change TX Hash:", supplyResult.hash);
console.log("[CreateRoom] Supply Change TX Announced Successfully!");

} catch (txErr) {
Expand Down
11 changes: 6 additions & 5 deletions Backend/Workspace/Routes/SendTokenByNFC.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ router.post('/NFC/Submit', VCM('LOGIN_TOKEN', process.env.LOGIN_SECRET), async (
try {
const { sendtoUserID, Amount } = req.body;
const fromUserID = req.auth.userId;
const parsedAmount = Number(Amount);

if (!sendtoUserID || !Amount || isNaN(Amount)) {
if (!sendtoUserID || Number.isNaN(parsedAmount) || parsedAmount <= 0 || !Number.isInteger(parsedAmount)) {
return res.status(400).json({ message: '不正なパラメータです' });
}

Expand All @@ -85,7 +86,7 @@ router.post('/NFC/Submit', VCM('LOGIN_TOKEN', process.env.LOGIN_SECRET), async (
pendingTransfers.set(reservationID, {
fromUserID,
sendtoUserID,
Amount,
Amount: parsedAmount,
updatedAt: Date.now(),
processedUids: {} // 【追加】二重引き落とし防止のための履歴
});
Expand Down Expand Up @@ -167,7 +168,7 @@ router.post('/NFC', async (req, res) => {
if (!fromAddressInfo.length) throw new Error("送金元アドレスが見つかりません");

const currentAmount = await LeftTokenAmount(fromAddressInfo[0].Address, MosaicIDHex, nodeUrl);
const transferAmount = BigInt(Amount) * 1_000_000n;
const transferAmount = BigInt(Amount);
if (currentAmount < transferAmount) {
throw new Error("残高不足です");
}
Expand Down Expand Up @@ -205,11 +206,11 @@ router.post('/NFC', async (req, res) => {
networkType: 'testnet',
senderPrivateKey: decryptedPrivateKey,
recipientRawAddress: SendToAddress,
message: `Payment via NFC Gate`,
messageText: `Payment via NFC Gate`,
mosaics: [
{
mosaicId: BigInt(`0x${MosaicIDHex}`),
amount: BigInt(Amount) * 1_000_000n
amount: BigInt(Amount)
}
],
deadlineHours: 2,
Expand Down
8 changes: 6 additions & 2 deletions Backend/Workspace/Tools/CreateMosaicTx.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export function CreateMosaicTx({
networkType = 'testnet',
senderPrivateKey,
transferable = true,
duration = 86400n,
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default mosaic duration changed from effectively unlimited (0n) to 86400n. This is a behavior change that will make newly created mosaics expire after a fixed duration by default. If expiration is intended, consider requiring callers to pass duration explicitly (or at least documenting the units and rationale); otherwise consider keeping the previous default of 0n.

Suggested change
duration = 86400n,
duration = 0n, // 0 = unlimited duration (no expiration)

Copilot uses AI. Check for mistakes.
fee = 1_000_000n,
deadlineHours = 2
}) {

Expand All @@ -16,9 +18,10 @@ export function CreateMosaicTx({
const privateKey = new PrivateKey(senderPrivateKey.trim());
const keyPair = facade.createAccount(privateKey);

const safeDeadlineHours = Math.min(Math.max(Number(deadlineHours) || 2, 1), 2);
const deadline = facade.network
.fromDatetime(new Date())
.addHours(Number(deadlineHours))
.addHours(safeDeadlineHours)
.timestamp;
Comment on lines +21 to 25
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safeDeadlineHours currently clamps deadlineHours to a maximum of 2 and also uses Number(deadlineHours) || 2 (so 0 becomes 2). Because callers in this repo pass deadlineHours: 24 (e.g. CreateRoom), this will silently override the caller’s request. Consider either throwing on out-of-range inputs or clearly documenting/renaming the parameter to reflect that it will be capped.

Copilot uses AI. Check for mistakes.

// uint32 nonce
Expand All @@ -40,7 +43,8 @@ export function CreateMosaicTx({
const mosaicDefinitionTx = facade.transactionFactory.create({
type: 'mosaic_definition_transaction_v1',
signerPublicKey: keyPair.publicKey,
duration: 0n,
fee: BigInt(fee),
duration: BigInt(duration),
nonce: nonce,
Comment on lines +46 to 48
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fee and duration are coerced via BigInt(...) but not validated. Passing a negative value (or a non-integer number) can create an invalid transaction or throw unexpectedly at runtime. Add input validation to ensure both are non-negative integers before building the transaction.

Copilot uses AI. Check for mistakes.
flags: flags,
divisibility: 0,
Expand Down
3 changes: 2 additions & 1 deletion Backend/Workspace/Tools/CreateTransferTx.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export default function CreateTransferTx({
const keyPair = facade.createAccount(privateKeyObject);

// Deadline 作成 (v3のfromDatetimeはDateオブジェクトを受け取ります)
const deadline = facade.network.fromDatetime(new Date()).addHours(Number(deadlineHours)).timestamp;
const safeDeadlineHours = Math.min(Math.max(Number(deadlineHours) || 2, 1), 2);
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safeDeadlineHours uses Number(deadlineHours) || 2, which treats a valid 0 as "unset" and silently clamps any value > 2 down to 2. If the intent is to enforce a valid deadline range, it’s safer to explicitly validate with Number.isFinite(...) and either clamp with clear semantics (using ?? instead of ||) or throw when the caller passes an out-of-range value.

Suggested change
const safeDeadlineHours = Math.min(Math.max(Number(deadlineHours) || 2, 1), 2);
let parsedDeadlineHours = Number(deadlineHours);
if (!Number.isFinite(parsedDeadlineHours) || parsedDeadlineHours === 0) {
parsedDeadlineHours = 2;
}
const safeDeadlineHours = Math.min(Math.max(parsedDeadlineHours, 1), 2);

Copilot uses AI. Check for mistakes.
const deadline = facade.network.fromDatetime(new Date()).addHours(safeDeadlineHours).timestamp;
console.log(`[${logOwner}] Intermediate => KeyPair created, Deadline calculated`);

// メッセージの作成
Expand Down
59 changes: 56 additions & 3 deletions Backend/Workspace/Tools/SignAndAnnounce.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
import { PrivateKey } from 'symbol-sdk';
import { SymbolFacade } from 'symbol-sdk/symbol';
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SymbolFacade is imported but not used in this module. Please remove the unused import to avoid confusion and keep the dependency surface minimal.

Suggested change
import { SymbolFacade } from 'symbol-sdk/symbol';

Copilot uses AI. Check for mistakes.

export default async function SignAndAnnounce(tx, privateKey, facade, nodeUrl) {
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function waitForConfirmation(nodeUrl, hash, {
timeoutMs = 120000,
intervalMs = 2000
} = {}) {
const startedAt = Date.now();

while (Date.now() - startedAt < timeoutMs) {
const confirmedRes = await fetch(`${nodeUrl}/transactions/confirmed/${hash}`);
if (confirmedRes.ok) {
return { confirmed: true };
}

const statusRes = await fetch(`${nodeUrl}/transactionStatus/${hash}`);
if (statusRes.ok) {
const statusJson = await statusRes.json();
const code = statusJson?.code;
if (code && code !== 'Success') {
throw new Error(`Transaction rejected: ${code}`);
}
Comment on lines +13 to +24
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

waitForConfirmation will abort immediately if fetch(...) throws (e.g., transient network/DNS/TLS error), instead of continuing to poll until timeoutMs. Wrap the polling HTTP calls in a try/catch (or handle non-HTTP failures) so temporary node connectivity issues don’t cause a false failure.

Suggested change
const confirmedRes = await fetch(`${nodeUrl}/transactions/confirmed/${hash}`);
if (confirmedRes.ok) {
return { confirmed: true };
}
const statusRes = await fetch(`${nodeUrl}/transactionStatus/${hash}`);
if (statusRes.ok) {
const statusJson = await statusRes.json();
const code = statusJson?.code;
if (code && code !== 'Success') {
throw new Error(`Transaction rejected: ${code}`);
}
try {
const confirmedRes = await fetch(`${nodeUrl}/transactions/confirmed/${hash}`);
if (confirmedRes.ok) {
return { confirmed: true };
}
const statusRes = await fetch(`${nodeUrl}/transactionStatus/${hash}`);
if (statusRes.ok) {
const statusJson = await statusRes.json();
const code = statusJson?.code;
if (code && code !== 'Success') {
throw new Error(`Transaction rejected: ${code}`);
}
}
} catch (error) {
// Network / transport / parsing errors: log and continue polling until timeout
console.warn(`waitForConfirmation: transient error while polling ${hash} at ${nodeUrl}:`, error);

Copilot uses AI. Check for mistakes.
}

await sleep(intervalMs);
}

throw new Error(`Transaction confirmation timeout: ${hash}`);
}

export default async function SignAndAnnounce(tx, privateKey, facade, nodeUrl, options = {}) {
// Startup Log
const logOwner = "SignAndAnnounce";
console.log(`\n${logOwner}-Function is running!\n`);
const {
waitForConfirmation: shouldWaitForConfirmation = false,
confirmationTimeoutMs = 120000,
pollIntervalMs = 2000
} = options;

// I/O Log
// ※ テンプレートリテラル( `${}` )内でオブジェクトを呼ぶと [object Object] になってしまうため、
Expand All @@ -24,9 +58,13 @@ export default async function SignAndAnnounce(tx, privateKey, facade, nodeUrl) {
try {
// 署名 (v3 SDK)
const signature = facade.signTransaction(account.keyPair, tx);
// !!!ここを追加!!!
if (!tx.signature?.bytes || !signature?.bytes) {
throw new Error('Transaction signature buffer is invalid');
}
tx.signature.bytes.set(signature.bytes);

const hash = facade.hashTransaction(tx).toString();
console.log(`[Debug] Transaction Hash: ${hash}`);
console.log(`[${logOwner}] Transaction Hash: ${hash}`);

// 署名付きトランザクションペイロード作成 (v3 SDK)
// ※ v3では attachSignature が JSON形式の文字列('{"payload": "..."}')を返すのが標準的です。
Expand Down Expand Up @@ -55,6 +93,21 @@ export default async function SignAndAnnounce(tx, privateKey, facade, nodeUrl) {

console.log(`[${logOwner}] Successfully announced transaction!`);

if (shouldWaitForConfirmation) {
console.log(`[${logOwner}] Waiting for confirmation...`);
await waitForConfirmation(nodeUrl, hash, {
timeoutMs: confirmationTimeoutMs,
intervalMs: pollIntervalMs
});
console.log(`[${logOwner}] Transaction confirmed!`);
}

return {
hash,
announced: true,
confirmed: shouldWaitForConfirmation
};

} catch (error) {
console.error(`[${logOwner}] Error:`, error);
throw error; // ルーター側のcatchで捕まえられるようにエラーを再スローします
Expand Down
13 changes: 10 additions & 3 deletions Backend/Workspace/Tools/SupplyMosaic.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const CreateSupplyTx = ({
senderPrivateKey,
supply = 1_000_000n,
mosaicId,
fee = 1_000_000n,
deadlineHours = 2
}) => {

Expand All @@ -19,6 +20,10 @@ export const CreateSupplyTx = ({
if (!mosaicId)
throw new Error("mosaicId is undefined");

const supplyDelta = BigInt(supply);
if (supplyDelta <= 0n)
throw new Error("supply must be greater than 0");

// Facade
const facade = new SymbolFacade(networkType);

Expand All @@ -27,19 +32,21 @@ export const CreateSupplyTx = ({
const keyPair = facade.createAccount(privateKey);

// Deadline
const safeDeadlineHours = Math.min(Math.max(Number(deadlineHours) || 2, 1), 2);
const deadline = facade.network
.fromDatetime(new Date())
.addHours(Number(deadlineHours))
.addHours(safeDeadlineHours)
.timestamp;
Comment on lines +35 to 39
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safeDeadlineHours clamps deadlineHours to a maximum of 2 and uses Number(deadlineHours) || 2, which silently overrides caller-provided values (including treating 0 as default). Consider explicit validation (e.g., Number.isFinite) and either throwing for out-of-range values or making the capping behavior explicit to callers.

Copilot uses AI. Check for mistakes.

// ★ ここ重要
const supplyTx = facade.transactionFactory.create({
type: 'mosaic_supply_change_transaction_v1',
signerPublicKey: keyPair.publicKey,
fee: BigInt(fee),

mosaicId: BigInt('0x' + mosaicId), // 必ずBigInt
delta: BigInt(supply), // 必ずBigInt
action: 0, // ★ increaseは0
delta: supplyDelta, // 必ずBigInt
action: 1, // increase は 1

deadline
});
Expand Down
Loading