UNPKG

archethic

Version:
315 lines 13.9 kB
import { Curve, HashAlgorithm, UserTypeTransaction } from "./types.js"; import { bigIntToUint8Array, concatUint8Arrays, intToUint8Array, maybeHexToUint8Array, maybeStringToUint8Array, sortObjectKeysASC, toByteArray, uint8ArrayToHex } from "./utils.js"; import { deriveAddress, deriveKeyPair, sign } from "./crypto.js"; const VERSION = 2; function getTransactionTypeId(type) { switch (type) { case UserTypeTransaction.keychain: return 255; case UserTypeTransaction.keychain_access: return 254; case UserTypeTransaction.transfer: return 253; case UserTypeTransaction.hosting: return 252; case UserTypeTransaction.token: return 251; case UserTypeTransaction.data: return 250; case UserTypeTransaction.contract: return 249; case UserTypeTransaction.code_proposal: return 5; case UserTypeTransaction.code_approval: return 6; } } export default class TransactionBuilder { version; type; address; data; previousPublicKey; previousSignature; originSignature; constructor(type = UserTypeTransaction.transfer) { this.version = VERSION; this.type = type; this.address = new Uint8Array(); this.data = { content: new Uint8Array(), code: new Uint8Array(), ownerships: [], ledger: { uco: { transfers: [] }, token: { transfers: [] } }, recipients: [] }; this.previousPublicKey = new Uint8Array(); this.previousSignature = new Uint8Array(); this.originSignature = new Uint8Array(); } setType(type) { if (!Object.keys(UserTypeTransaction).includes(type)) { throw "Transaction type must be one of " + Object.keys(UserTypeTransaction).map(t => `'${t}'`).join(", "); } this.type = type; return this; } setCode(code) { this.data.code = new TextEncoder().encode(code); return this; } setContent(content) { if (typeof (content) == "string") { content = new TextEncoder().encode(content); } this.data.content = content; return this; } addOwnership(secret, authorizedKeys) { secret = maybeStringToUint8Array(secret); if (!Array.isArray(authorizedKeys)) { throw 'Authorized keys must be an array'; } const filteredAuthorizedKeys = []; const acc = new Map(); authorizedKeys.forEach(({ publicKey, encryptedSecretKey }) => { if (acc.has(publicKey)) return; filteredAuthorizedKeys.push({ publicKey: maybeHexToUint8Array(publicKey), encryptedSecretKey: maybeHexToUint8Array(encryptedSecretKey) }); acc.set(publicKey, encryptedSecretKey); }); this.data.ownerships.push({ secret: secret, authorizedPublicKeys: filteredAuthorizedKeys }); return this; } addUCOTransfer(to, amount) { to = maybeHexToUint8Array(to); if (isNaN(amount) || amount <= 0) { throw 'UCO transfer amount must be a positive number'; } this.data.ledger.uco.transfers.push({ to, amount }); return this; } addTokenTransfer(to, amount, tokenAddress, tokenId = 0) { to = maybeHexToUint8Array(to); tokenAddress = maybeHexToUint8Array(tokenAddress); if (isNaN(amount) || amount <= 0) { throw 'Token transfer amount must be a positive number'; } if (isNaN(tokenId) || tokenId < 0) { throw "'tokenId' must be a valid integer >= 0"; } this.data.ledger.token.transfers.push({ to: to, amount: amount, tokenAddress: tokenAddress, tokenId: tokenId }); return this; } addRecipient(to, action, args) { const address = maybeHexToUint8Array(to); if (action && typeof action != 'string') { throw '`action` must be a string'; } if (args && !Array.isArray(args)) { throw '`args` must be an array'; } if (action && !args) { args = []; } this.data.recipients.push({ address, action, args }); return this; } setPreviousSignatureAndPreviousPublicKey(prevSign, prevPubKey) { prevSign = maybeHexToUint8Array(prevSign); prevPubKey = maybeHexToUint8Array(prevPubKey); this.previousPublicKey = prevPubKey; this.previousSignature = prevSign; return this; } setAddress(addr) { addr = maybeHexToUint8Array(addr); this.address = addr; return this; } build(seed, index, curve = "ed25519", hashAlgo = "sha256") { if (!Object.keys(Curve).includes(curve)) { throw "Curve must be one of " + Object.keys(Curve).map(t => `'${t}'`).join(", "); } if (!Object.keys(HashAlgorithm).includes(hashAlgo)) { throw "Hash algorithm must be one of " + Object.keys(HashAlgorithm).map(t => `'${t}'`).join(", "); } const keypair = deriveKeyPair(seed, index, curve); this.address = deriveAddress(seed, index + 1, curve, hashAlgo); this.previousPublicKey = keypair.publicKey; const payloadForPreviousSignature = this.previousSignaturePayload(); this.previousSignature = sign(payloadForPreviousSignature, keypair.privateKey); return this; } originSign(privateKey) { privateKey = maybeHexToUint8Array(privateKey); this.originSignature = sign(this.originSignaturePayload(), privateKey); return this; } setOriginSign(signature) { signature = maybeHexToUint8Array(signature); this.originSignature = signature; return this; } originSignaturePayload() { const payloadForPreviousSignature = this.previousSignaturePayload(); return concatUint8Arrays(payloadForPreviousSignature, this.previousPublicKey, Uint8Array.from([this.previousSignature.length]), this.previousSignature); } previousSignaturePayload() { const bufCodeSize = intToUint8Array(this.data.code.length); let contentSize = this.data.content.length; const bufContentSize = intToUint8Array(contentSize); const ownershipsBuffer = this.data.ownerships.map(({ secret, authorizedPublicKeys }) => { const bufAuthKeyLength = toByteArray(authorizedPublicKeys.length); const authorizedKeysBuffer = [Uint8Array.from([bufAuthKeyLength.length]), bufAuthKeyLength]; authorizedPublicKeys.sort((a, b) => uint8ArrayToHex(a.publicKey).localeCompare(uint8ArrayToHex(b.publicKey))); authorizedPublicKeys.forEach(({ publicKey, encryptedSecretKey }) => { authorizedKeysBuffer.push(maybeHexToUint8Array(publicKey)); authorizedKeysBuffer.push(maybeHexToUint8Array(encryptedSecretKey)); }); return concatUint8Arrays(intToUint8Array(secret.byteLength), secret, concatUint8Arrays(...authorizedKeysBuffer)); }); const ucoTransfersBuffers = this.data.ledger.uco.transfers.map(function (transfer) { return concatUint8Arrays(transfer.to, bigIntToUint8Array(transfer.amount)); }); const tokenTransfersBuffers = this.data.ledger.token.transfers.map(function (transfer) { const bufTokenId = toByteArray(transfer.tokenId); return concatUint8Arrays(transfer.tokenAddress, transfer.to, bigIntToUint8Array(transfer.amount), Uint8Array.from([bufTokenId.length]), bufTokenId); }); const recipientsBuffer = this.data.recipients.map(({ address, action, args }) => { if (action == undefined || args == undefined) { return concatUint8Arrays(Uint8Array.from([0]), address); } else { const orderedArgs = args.map((arg) => sortObjectKeysASC(arg)); const jsonArgs = JSON.stringify(orderedArgs); const bufJsonLength = toByteArray(jsonArgs.length); return concatUint8Arrays(Uint8Array.from([1]), address, Uint8Array.from([action.length]), new TextEncoder().encode(action), Uint8Array.from([bufJsonLength.length]), bufJsonLength, new TextEncoder().encode(jsonArgs)); } }); const bufOwnershipLength = toByteArray(this.data.ownerships.length); const bufUCOTransferLength = toByteArray(this.data.ledger.uco.transfers.length); const bufTokenTransferLength = toByteArray(this.data.ledger.token.transfers.length); const bufRecipientLength = toByteArray(this.data.recipients.length); return concatUint8Arrays(intToUint8Array(VERSION), this.address, Uint8Array.from([getTransactionTypeId(this.type)]), bufCodeSize, this.data.code, bufContentSize, this.data.content, Uint8Array.from([bufOwnershipLength.length]), bufOwnershipLength, ...ownershipsBuffer, Uint8Array.from([bufUCOTransferLength.length]), bufUCOTransferLength, ...ucoTransfersBuffers, Uint8Array.from([bufTokenTransferLength.length]), bufTokenTransferLength, ...tokenTransfersBuffers, Uint8Array.from([bufRecipientLength.length]), bufRecipientLength, ...recipientsBuffer); } toNodeRPC() { return { version: this.version, address: uint8ArrayToHex(this.address), type: this.type, data: { content: new TextDecoder().decode(this.data.content), code: new TextDecoder().decode(this.data.code), ownerships: this.data.ownerships.map(({ secret, authorizedPublicKeys }) => { return { secret: uint8ArrayToHex(secret), authorizedKeys: authorizedPublicKeys.map(({ publicKey, encryptedSecretKey }) => { return { publicKey: uint8ArrayToHex(publicKey), encryptedSecretKey: uint8ArrayToHex(encryptedSecretKey) }; }) }; }), ledger: { uco: { transfers: this.data.ledger.uco.transfers.map((t) => { return { to: uint8ArrayToHex(t.to), amount: t.amount }; }) }, token: { transfers: this.data.ledger.token.transfers.map((t) => { return { to: uint8ArrayToHex(t.to), amount: t.amount, tokenAddress: uint8ArrayToHex(t.tokenAddress), tokenId: t.tokenId }; }) } }, recipients: this.data.recipients.map(({ address, action, args }) => { return { address: uint8ArrayToHex(address), action: action, args: args }; }) }, previousPublicKey: uint8ArrayToHex(this.previousPublicKey), previousSignature: uint8ArrayToHex(this.previousSignature), originSignature: this.originSignature && uint8ArrayToHex(this.originSignature) }; } toJSON() { return JSON.stringify(this.toNodeRPC()); } toRPC() { return { version: this.version, type: this.type, data: { content: new TextDecoder().decode(this.data.content), code: new TextDecoder().decode(this.data.code), ownerships: this.data.ownerships.map(({ secret, authorizedPublicKeys }) => { return { secret: uint8ArrayToHex(secret), authorizedKeys: authorizedPublicKeys.map(({ publicKey, encryptedSecretKey }) => { return { publicKey: uint8ArrayToHex(publicKey), encryptedSecretKey: uint8ArrayToHex(encryptedSecretKey) }; }) }; }), ledger: { uco: { transfers: this.data.ledger.uco.transfers.map((t) => { return { to: uint8ArrayToHex(t.to), amount: t.amount }; }) }, token: { transfers: this.data.ledger.token.transfers.map((t) => { return { to: uint8ArrayToHex(t.to), amount: t.amount, tokenAddress: uint8ArrayToHex(t.tokenAddress), tokenId: t.tokenId }; }) } }, recipients: this.data.recipients.map(({ address, action, args }) => { return { address: uint8ArrayToHex(address), action: action, args: args }; }) }, }; } } //# sourceMappingURL=transaction_builder.js.map