PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-sol/test/unit/transactionBuilder
Просмотр файла: transactionBuilder.ts
import should from 'should';
import * as bs58 from 'bs58';
import { getBuilderFactory } from '../getBuilderFactory';
import { KeyPair, TokenTransferBuilder } from '../../../src';
import { Eddsa, TransactionType } from '@bitgo/sdk-core';
import * as testData from '../../resources/sol';
import BigNumber from 'bignumber.js';
import { Ed25519Bip32HdTree } from '@bitgo/sdk-lib-mpc';
describe('Sol Transaction Builder', async () => {
let builders;
const factory = getBuilderFactory('tsol');
const authAccount = new KeyPair(testData.authAccount).getKeys();
const nonceAccount = new KeyPair(testData.nonceAccount).getKeys();
const validBlockhash = 'GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi';
beforeEach(function (done) {
builders = [
factory.getWalletInitializationBuilder(),
factory.getTransferBuilder(),
factory.getStakingActivateBuilder(),
factory.getStakingWithdrawBuilder(),
];
done();
});
it('start and build an empty a transfer tx with fee', async () => {
const txBuilder = factory.getTransferBuilder();
txBuilder.sender(authAccount.pub);
txBuilder.nonce(validBlockhash);
txBuilder.fee({ amount: 5000 });
const tx = await txBuilder.build();
should.equal(tx.type, TransactionType.Send);
const txJson = tx.toJson();
txJson.lamportsPerSignature?.should.equal(5000);
});
it('should fail to build if missing sender', async () => {
for (const txBuilder of builders) {
txBuilder.nonce(validBlockhash);
await txBuilder.build().should.rejectedWith('Invalid transaction: missing sender');
}
});
it('should fail to build if missing nonce', async () => {
for (const txBuilder of builders) {
txBuilder.sender(authAccount.pub);
await txBuilder.build().should.rejectedWith('Invalid transaction: missing nonce blockhash');
}
});
it('build a wallet init from rawTx', async () => {
const txBuilder = factory.from(testData.WALLET_INIT_SIGNED_TX);
const builtTx = await txBuilder.build();
should.equal(builtTx.type, TransactionType.WalletInitialization);
should.equal(builtTx.id, '7TkU8wLgXDeLFbVydtg6mqMsp9GatsetitSngysgjxFhofKSUcLPBoKPHciLeGEfJFMsqezpZmGRSFQTBy7ZDsg');
builtTx.inputs.length.should.equal(1);
builtTx.inputs[0].should.deepEqual({
address: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe',
value: testData.tokenTransfers.amount.toString(),
coin: 'tsol',
});
builtTx.outputs.length.should.equal(0);
const jsonTx = builtTx.toJson();
jsonTx.id.should.equal('7TkU8wLgXDeLFbVydtg6mqMsp9GatsetitSngysgjxFhofKSUcLPBoKPHciLeGEfJFMsqezpZmGRSFQTBy7ZDsg');
jsonTx.feePayer.should.equal('5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe');
jsonTx.nonce.should.equal('GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi');
jsonTx.numSignatures.should.equal(2);
jsonTx.instructionsData.should.deepEqual([
{
type: 'CreateNonceAccount',
params: {
fromAddress: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe',
nonceAddress: '8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh',
authAddress: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe',
amount: '300000',
},
},
]);
builtTx.toBroadcastFormat().should.equal(testData.WALLET_INIT_SIGNED_TX);
});
it('build a staking activate from rawTx', async () => {
const txBuilder = factory.from(testData.STAKING_ACTIVATE_SIGNED_TX);
const builtTx = await txBuilder.build();
should.equal(builtTx.type, TransactionType.StakingActivate);
should.equal(builtTx.id, 'DCsSiGuKiWgtFRF2ZCh5x6xukApffYDs5Y9CyvYBEebMVnXH5TydKpT76srTSr1AhvDZqsnS5EVhvkS8Rzh91hH');
builtTx.inputs.length.should.equal(1);
builtTx.inputs[0].should.deepEqual({
address: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe',
value: testData.tokenTransfers.amount.toString(),
coin: 'tsol',
});
builtTx.outputs.length.should.equal(1);
const jsonTx = builtTx.toJson();
jsonTx.id.should.equal('DCsSiGuKiWgtFRF2ZCh5x6xukApffYDs5Y9CyvYBEebMVnXH5TydKpT76srTSr1AhvDZqsnS5EVhvkS8Rzh91hH');
jsonTx.feePayer.should.equal('5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe');
jsonTx.nonce.should.equal('GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi');
jsonTx.numSignatures.should.equal(2);
jsonTx.instructionsData.should.deepEqual([
{
type: 'Activate',
params: {
fromAddress: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe',
stakingAddress: '7dRuGFbU2y2kijP6o1LYNzVyz4yf13MooqoionCzv5Za',
amount: '300000',
validator: 'CyjoLt3kjqB57K7ewCBHmnHq3UgEj3ak6A7m6EsBsuhA',
isMarinade: false,
},
},
]);
builtTx.toBroadcastFormat().should.equal(testData.STAKING_ACTIVATE_SIGNED_TX);
});
it('build a send from rawTx', async () => {
const txBuilder = factory.from(testData.TRANSFER_SIGNED_TX_WITH_MEMO_AND_DURABLE_NONCE);
const builtTx = await txBuilder.build();
should.equal(builtTx.type, TransactionType.Send);
should.equal(
builtTx.id,
'2XFxGfXddKWnqGaMAsfNL8HgXqDvjBL2Ae28KWrRvg9bQBmCrpHYVDacuZFeAUyYwjXG6ey2jTARX5VQCnj7SF4L'
);
builtTx.inputs.length.should.equal(1);
builtTx.inputs[0].should.deepEqual({
address: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe',
value: testData.tokenTransfers.amount.toString(),
coin: 'tsol',
});
builtTx.outputs.length.should.equal(1);
builtTx.outputs[0].should.deepEqual({
address: 'CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S',
value: testData.tokenTransfers.amount.toString(),
coin: 'tsol',
});
const jsonTx = builtTx.toJson();
jsonTx.id.should.equal('2XFxGfXddKWnqGaMAsfNL8HgXqDvjBL2Ae28KWrRvg9bQBmCrpHYVDacuZFeAUyYwjXG6ey2jTARX5VQCnj7SF4L');
jsonTx.feePayer.should.equal('5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe');
jsonTx.nonce.should.equal('GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi');
jsonTx.numSignatures.should.equal(1);
jsonTx.durableNonce.should.deepEqual({
walletNonceAddress: '8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh',
authWalletAddress: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe',
});
jsonTx.instructionsData.should.deepEqual([
{
type: 'Transfer',
params: {
fromAddress: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe',
toAddress: 'CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S',
amount: '300000',
},
},
{
type: 'Memo',
params: { memo: 'test memo' },
},
]);
builtTx.toBroadcastFormat().should.equal(testData.TRANSFER_SIGNED_TX_WITH_MEMO_AND_DURABLE_NONCE);
});
it('build a send from raw token transaction', async () => {
const txBuilder = factory.from(
testData.TOKEN_TRANSFER_SIGNED_TX_WITH_MEMO_AND_DURABLE_NONCE
) as TokenTransferBuilder;
const prioFeeMicroLamports = '10000000';
// We don't have to manually set the priority fee here as the raw txn already has the priority fee instruction;
// therefore once initBuilder is called (it's called within fromImplementation), it will set the txBuilder's priorityFee field
// which will then be used in txBuilder.build() by tokenTransferBuilder to add the set compute fee instruction
const builtTx = await txBuilder.build();
should.equal(builtTx.type, TransactionType.Send);
should.equal(
builtTx.id,
'2ticU4ZkEqdTHULr6LobTgWBhim6E7wSscDhM4gzyuGUmQyUwLYhoqaifuvwmNzzEf1T5aefVcgMQkSHdJ5nsrfZ'
);
builtTx.inputs.length.should.equal(1);
builtTx.inputs[0].should.deepEqual({
address: testData.associatedTokenAccounts.accounts[0].pub,
value: testData.tokenTransfers.amount.toString(),
coin: 'tsol:usdc',
});
builtTx.outputs.length.should.equal(1);
builtTx.outputs[0].should.deepEqual({
address: 'CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S',
value: testData.tokenTransfers.amount.toString(),
coin: 'tsol:usdc',
});
const jsonTx = builtTx.toJson();
jsonTx.id.should.equal('2ticU4ZkEqdTHULr6LobTgWBhim6E7wSscDhM4gzyuGUmQyUwLYhoqaifuvwmNzzEf1T5aefVcgMQkSHdJ5nsrfZ');
jsonTx.feePayer.should.equal(testData.associatedTokenAccounts.accounts[0].pub);
jsonTx.nonce.should.equal('GHtXQBsoZHVnNFa9YevAzFr17DJjgHXk3ycTKD5xD3Zi');
jsonTx.numSignatures.should.equal(1);
jsonTx.durableNonce.should.deepEqual({
walletNonceAddress: '8Y7RM6JfcX4ASSNBkrkrmSbRu431YVi9Y3oLFnzC2dCh',
authWalletAddress: testData.associatedTokenAccounts.accounts[0].pub,
});
const priorityFeeBigInt = BigInt(prioFeeMicroLamports);
jsonTx.instructionsData.should.deepEqual([
{
params: {
fee: priorityFeeBigInt,
},
type: 'SetPriorityFee',
},
{
type: 'TokenTransfer',
params: {
fromAddress: testData.associatedTokenAccounts.accounts[0].pub,
toAddress: 'CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S',
amount: testData.tokenTransfers.amount.toString(),
tokenName: testData.tokenTransfers.nameUSDC.toString(),
sourceAddress: 'B5rJjuVi7En63iK6o3ijKdJwAoTe2gwCYmJsVdHQ2aKV',
},
},
{
type: 'Memo',
params: { memo: 'test memo' },
},
]);
builtTx.toBroadcastFormat().should.equal(testData.TOKEN_TRANSFER_SIGNED_TX_WITH_MEMO_AND_DURABLE_NONCE);
});
describe('Nonce tests', async () => {
it('should throw for invalid nonce', () => {
const blockHash = 'randomstring';
for (const txBuilder of builders) {
should(() => txBuilder.nonce(blockHash)).throw('Invalid or missing blockHash, got: ' + blockHash);
}
});
it('should throw for invalid params using durable nonce', () => {
const invalidPubKey = 'randomstring';
for (const txBuilder of builders) {
should(() =>
txBuilder.nonce(validBlockhash, {
walletNonceAddress: invalidPubKey,
authWalletAddress: authAccount.pub,
})
).throw('Invalid or missing walletNonceAddress, got: ' + invalidPubKey);
should(() =>
txBuilder.nonce(validBlockhash, {
walletNonceAddress: nonceAccount.pub,
authWalletAddress: invalidPubKey,
})
).throw('Invalid or missing authWalletAddress, got: ' + invalidPubKey);
should(() =>
txBuilder.nonce(validBlockhash, {
walletNonceAddress: nonceAccount.pub,
authWalletAddress: nonceAccount.pub,
})
).throw('Invalid params: walletNonceAddress cannot be equal to authWalletAddress');
}
});
it('should succeed for valid nonce', () => {
for (const txBuilder of builders) {
should.doesNotThrow(() => txBuilder.nonce(validBlockhash));
}
});
it('should succeed for valid durable nonce', () => {
for (const txBuilder of builders) {
should.doesNotThrow(() =>
txBuilder.nonce(validBlockhash, { walletNonceAddress: nonceAccount.pub, authWalletAddress: authAccount.pub })
);
}
});
});
describe('sender tests', async () => {
it('should throw for invalid sender', () => {
const invalidPublicKey = 'randomstring';
for (const txBuilder of builders) {
should(() => txBuilder.sender(invalidPublicKey)).throw('Invalid or missing sender, got: ' + invalidPublicKey);
}
});
it('should succeed for valid sender', () => {
for (const txBuilder of builders) {
should.doesNotThrow(() => txBuilder.sender(authAccount.pub));
}
});
});
it('validateAddress', () => {
const validAddress = { address: authAccount.pub };
const invalidAddress = { address: testData.pubKeys.invalidPubKeys[0] };
for (const builder of builders) {
should.doesNotThrow(() => builder.validateAddress(validAddress));
should(() => builder.validateAddress(invalidAddress)).throwError(
'Invalid address ' + testData.pubKeys.invalidPubKeys[0]
);
}
});
it('validateKey', () => {
const validKey = { key: testData.authAccount.prv };
const invalidKey = { key: testData.authAccount.pub };
for (const builder of builders) {
should.doesNotThrow(() => builder.validateKey(validKey));
should(() => builder.validateKey(invalidKey)).throwError('Invalid key');
}
});
it('validateRawTransaction', () => {
const validRawTx = testData.RAW_TX_SIGNED;
const invalidRawTx = 'AAAAAAAAAAAAAAAAAAA';
for (const builder of builders) {
should.doesNotThrow(() => builder.validateRawTransaction(validRawTx));
should(() => builder.validateRawTransaction(invalidRawTx)).throwError('Invalid raw transaction');
}
});
it('validateValue', () => {
const validValue = new BigNumber('100000');
const invalidValue = new BigNumber('-100000');
for (const builder of builders) {
should.doesNotThrow(() => builder.validateValue(validValue));
should(() => builder.validateValue(invalidValue)).throwError('Value cannot be less than zero');
}
});
it('validateMemo', () => {
const validMemo = 'test memo';
const invalidMemo =
'ooawindaonmawñdamwdoianwdonalskdnaiouwbdoinowadn90awndoawndaowdnaioiuwbdioauwndaoudnbawodnba9owudbnaowdnaasdijhaksdkjasdkjhaskdjhkasdasdasdasdasd';
for (const builder of builders) {
builder._instructionsData.push({
type: 'Transfer',
params: {
fromAddress: '5hr5fisPi6DXNuuRpm5XUbzpiEnmdyxXuBDTwzwZj5Pe',
toAddress: 'CP5Dpaa42RtJmMuKqCQsLwma5Yh3knuvKsYDFX85F41S',
amount: '300000',
},
});
builder.validateMemo(validMemo);
should(() => builder.validateMemo(invalidMemo)).throwError('Memo is too long');
should((memo: string) => builder.validateMemo(memo)).throwError('Invalid memo, got: undefined');
}
});
describe('add signature', () => {
let MPC: Eddsa;
it('should add signature to transaction', async () => {
const transferBuilder = factory
.getTransferBuilder()
.sender(authAccount.pub)
.nonce(validBlockhash)
.fee({ amount: 5000 })
.send({ address: nonceAccount.pub, amount: '1000' });
transferBuilder.sign({ key: authAccount.prv });
const signedTransaction = await transferBuilder.build();
// signature is base58 encoded
const signature = signedTransaction.signature[0];
// verify rebuilt transaction contains signature
const rawTransaction = signedTransaction.toBroadcastFormat() as string;
const rebuiltSignedTransaction = await factory.from(rawTransaction).build();
rebuiltSignedTransaction.signature.should.deepEqual(signedTransaction.signature);
const transferBuilder2 = factory
.getTransferBuilder()
.sender(authAccount.pub)
.nonce(validBlockhash)
.fee({ amount: 5000 })
.send({ address: nonceAccount.pub, amount: '1000' });
transferBuilder2.addSignature({ pub: authAccount.pub }, Buffer.from(bs58.decode(signature)));
const signedTransaction2 = await transferBuilder2.build();
// verify signatures are correct
signedTransaction.signature.should.deepEqual(signedTransaction2.signature);
// verify rebuilt transaction contains signature
const rawTransaction2 = signedTransaction2.toBroadcastFormat() as string;
const rebuiltTransaction2 = await factory.from(rawTransaction2).build();
rebuiltTransaction2.signature.should.deepEqual(signedTransaction2.signature);
});
before('initialize mpc module', async () => {
const hdTree = await Ed25519Bip32HdTree.initialize();
MPC = await Eddsa.initialize(hdTree);
});
it('should add TSS signature', async () => {
const A = MPC.keyShare(1, 2, 3);
const B = MPC.keyShare(2, 2, 3);
const C = MPC.keyShare(3, 2, 3);
const A_combine = MPC.keyCombine(A.uShare, [B.yShares[1], C.yShares[1]]);
const B_combine = MPC.keyCombine(B.uShare, [A.yShares[2], C.yShares[2]]);
const C_combine = MPC.keyCombine(C.uShare, [A.yShares[3], B.yShares[3]]);
const commonPub = A_combine.pShare.y;
const solPublicKey = new KeyPair({ pub: commonPub });
const sender = solPublicKey.getAddress();
let transferBuilder = factory
.getTransferBuilder()
.sender(sender)
.nonce(validBlockhash)
.fee({ amount: 5000 })
.send({ address: nonceAccount.pub, amount: '1000' });
const unsignedTransaction = await transferBuilder.build();
const signablePayload = unsignedTransaction.signablePayload;
// signing with A and B
let A_sign_share = MPC.signShare(signablePayload, A_combine.pShare, [A_combine.jShares[2]]);
let B_sign_share = MPC.signShare(signablePayload, B_combine.pShare, [B_combine.jShares[1]]);
let A_sign = MPC.sign(signablePayload, A_sign_share.xShare, [B_sign_share.rShares[1]], [C.yShares[1]]);
let B_sign = MPC.sign(signablePayload, B_sign_share.xShare, [A_sign_share.rShares[2]], [C.yShares[2]]);
let signature = MPC.signCombine([A_sign, B_sign]);
let rawSignature = Buffer.concat([Buffer.from(signature.R, 'hex'), Buffer.from(signature.sigma, 'hex')]);
transferBuilder = factory
.getTransferBuilder()
.sender(sender)
.nonce(validBlockhash)
.fee({ amount: 5000 })
.send({ address: nonceAccount.pub, amount: '1000' });
transferBuilder.addSignature({ pub: sender }, rawSignature);
let signedTransaction = await transferBuilder.build();
signedTransaction.signature.length.should.equal(1);
signedTransaction.signature[0].should.equal(bs58.encode(rawSignature));
signedTransaction.id.should.equal(bs58.encode(rawSignature));
// signing with A and C
A_sign_share = MPC.signShare(signablePayload, A_combine.pShare, [A_combine.jShares[3]]);
let C_sign_share = MPC.signShare(signablePayload, C_combine.pShare, [C_combine.jShares[1]]);
A_sign = MPC.sign(signablePayload, A_sign_share.xShare, [C_sign_share.rShares[1]], [B.yShares[1]]);
let C_sign = MPC.sign(signablePayload, C_sign_share.xShare, [A_sign_share.rShares[3]], [B.yShares[3]]);
signature = MPC.signCombine([A_sign, C_sign]);
rawSignature = Buffer.concat([Buffer.from(signature.R, 'hex'), Buffer.from(signature.sigma, 'hex')]);
transferBuilder = factory
.getTransferBuilder()
.sender(sender)
.nonce(validBlockhash)
.fee({ amount: 5000 })
.send({ address: nonceAccount.pub, amount: '1000' });
transferBuilder.addSignature({ pub: sender }, rawSignature);
signedTransaction = await transferBuilder.build();
signedTransaction.signature.length.should.equal(1);
signedTransaction.signature[0].should.equal(bs58.encode(rawSignature));
signedTransaction.id.should.equal(bs58.encode(rawSignature));
// signing with B and C
B_sign_share = MPC.signShare(signablePayload, B_combine.pShare, [B_combine.jShares[3]]);
C_sign_share = MPC.signShare(signablePayload, C_combine.pShare, [C_combine.jShares[2]]);
B_sign = MPC.sign(signablePayload, B_sign_share.xShare, [C_sign_share.rShares[2]], [A.yShares[2]]);
C_sign = MPC.sign(signablePayload, C_sign_share.xShare, [B_sign_share.rShares[3]], [A.yShares[3]]);
signature = MPC.signCombine([B_sign, C_sign]);
rawSignature = Buffer.concat([Buffer.from(signature.R, 'hex'), Buffer.from(signature.sigma, 'hex')]);
transferBuilder = factory
.getTransferBuilder()
.sender(sender)
.nonce(validBlockhash)
.fee({ amount: 5000 })
.send({ address: nonceAccount.pub, amount: '1000' });
transferBuilder.addSignature({ pub: sender }, rawSignature);
signedTransaction = await transferBuilder.build();
signedTransaction.signature.length.should.equal(1);
signedTransaction.signature[0].should.equal(bs58.encode(rawSignature));
signedTransaction.id.should.equal(bs58.encode(rawSignature));
const rawTransaction = signedTransaction.toBroadcastFormat() as string;
const rebuiltTransaction = await factory.from(rawTransaction).build();
rebuiltTransaction.id.should.equal(signedTransaction.id);
rebuiltTransaction.signature.should.deepEqual(signedTransaction.signature);
});
it('should add TSS HD signature', async () => {
const A = MPC.keyShare(1, 2, 3);
const B = MPC.keyShare(2, 2, 3);
const C = MPC.keyShare(3, 2, 3);
const A_combine = MPC.keyCombine(A.uShare, [B.yShares[1], C.yShares[1]]);
const B_combine = MPC.keyCombine(B.uShare, [A.yShares[2], C.yShares[2]]);
for (let index = 0; index < 10; index++) {
const path = `m/${index}`;
const A_subkey = MPC.keyDerive(A.uShare, [B.yShares[1], C.yShares[1]], path);
const B_subkey = MPC.keyCombine(B.uShare, [A_subkey.yShares[2], C.yShares[2]]);
const solPublicKey = new KeyPair({ pub: A_subkey.pShare.y });
const sender = solPublicKey.getAddress();
const transferBuilder = factory
.getTransferBuilder()
.sender(sender)
.nonce(validBlockhash)
.fee({ amount: 5000 })
.send({ address: nonceAccount.pub, amount: '1000' });
const unsignedTransaction = await transferBuilder.build();
const signablePayload = unsignedTransaction.signablePayload;
const A_sign_share = MPC.signShare(signablePayload, A_subkey.pShare, [A_combine.jShares[2]]);
const B_sign_share = MPC.signShare(signablePayload, B_subkey.pShare, [B_combine.jShares[1]]);
const A_sign = MPC.sign(signablePayload, A_sign_share.xShare, [B_sign_share.rShares[1]], [C.yShares[1]]);
const B_sign = MPC.sign(signablePayload, B_sign_share.xShare, [A_sign_share.rShares[2]], [C.yShares[2]]);
const signature = MPC.signCombine([A_sign, B_sign]);
const rawSignature = Buffer.concat([Buffer.from(signature.R, 'hex'), Buffer.from(signature.sigma, 'hex')]);
transferBuilder.addSignature({ pub: sender }, rawSignature);
const signedTransaction = await transferBuilder.build();
signedTransaction.signature.length.should.equal(1);
signedTransaction.signature[0].should.equal(bs58.encode(rawSignature));
signedTransaction.id.should.equal(bs58.encode(rawSignature));
const rawTransaction = signedTransaction.toBroadcastFormat() as string;
const rebuiltTransaction = await factory.from(rawTransaction).build();
rebuiltTransaction.id.should.equal(signedTransaction.id);
rebuiltTransaction.signature.should.deepEqual(signedTransaction.signature);
}
});
});
});
Выполнить команду
Для локальной разработки. Не используйте в интернете!