Add preliminary sendTokens() implementation
This commit is contained in:
parent
1c365dc31c
commit
29148ef898
|
@ -5,6 +5,7 @@ import fetch from 'node-fetch';
|
|||
import jayson from 'jayson/lib/client/browser';
|
||||
import nacl from 'tweetnacl';
|
||||
import {struct} from 'superstruct';
|
||||
import bs58 from 'bs58';
|
||||
|
||||
import type {Account, PublicKey} from './account';
|
||||
|
||||
|
@ -229,19 +230,53 @@ export class Connection {
|
|||
|
||||
/**
|
||||
* Send tokens to another account
|
||||
*
|
||||
* @todo THIS METHOD IS NOT FULLY IMPLEMENTED YET
|
||||
* @ignore
|
||||
*/
|
||||
async sendTokens(from: Account, to: PublicKey, amount: number): Promise<TransactionSignature> {
|
||||
const transaction = Buffer.from(
|
||||
// TODO: This is not the correct transaction payload
|
||||
`Transaction ${from.publicKey} ${to} ${amount}`
|
||||
);
|
||||
const lastId = await this.getLastId();
|
||||
const fee = 0;
|
||||
|
||||
const signedTransaction = nacl.sign.detached(transaction, from.secretKey);
|
||||
//
|
||||
// TODO: Redo this...
|
||||
//
|
||||
|
||||
const unsafeRes = await this._rpcRequest('sendTransaction', [[...signedTransaction]]);
|
||||
// Build the transaction data to be signed.
|
||||
const transactionData = Buffer.alloc(124);
|
||||
transactionData.writeUInt32LE(amount, 4); // u64
|
||||
transactionData.writeUInt32LE(amount - fee, 20); // u64
|
||||
transactionData.writeUInt32LE(32, 28); // length of public key (u64)
|
||||
{
|
||||
const toBytes = Buffer.from(bs58.decode(to));
|
||||
assert(toBytes.length === 32);
|
||||
toBytes.copy(transactionData, 36);
|
||||
}
|
||||
|
||||
transactionData.writeUInt32LE(32, 68); // length of last id (u64)
|
||||
{
|
||||
const lastIdBytes = Buffer.from(bs58.decode(lastId));
|
||||
assert(lastIdBytes.length === 32);
|
||||
lastIdBytes.copy(transactionData, 76);
|
||||
}
|
||||
|
||||
// Sign it
|
||||
const signature = nacl.sign.detached(transactionData, from.secretKey);
|
||||
assert(signature.length === 64);
|
||||
|
||||
// Build the over-the-wire transaction buffer
|
||||
const wireTransaction = Buffer.alloc(236);
|
||||
wireTransaction.writeUInt32LE(64, 0); // signature length (u64)
|
||||
Buffer.from(signature).copy(wireTransaction, 8);
|
||||
|
||||
|
||||
wireTransaction.writeUInt32LE(32, 72); // public key length (u64)
|
||||
{
|
||||
const fromBytes = Buffer.from(bs58.decode(from.publicKey));
|
||||
assert(fromBytes.length === 32);
|
||||
fromBytes.copy(wireTransaction, 80);
|
||||
}
|
||||
transactionData.copy(wireTransaction, 112);
|
||||
|
||||
// Send it
|
||||
const unsafeRes = await this._rpcRequest('sendTransaction', [[...wireTransaction]]);
|
||||
const res = SendTokensRpcResult(unsafeRes);
|
||||
if (res.error) {
|
||||
throw new Error(res.error.message);
|
||||
|
@ -251,4 +286,3 @@ export class Connection {
|
|||
return res.result;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// @flow
|
||||
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
type RpcRequest = {
|
||||
method: string;
|
||||
params: Array<any>;
|
||||
params?: Array<any>;
|
||||
};
|
||||
|
||||
type RpcResponseError = {
|
||||
|
@ -20,6 +22,13 @@ export const mockRpc: Array<[string, RpcRequest, RpcResponse]> = [];
|
|||
// eslint-disable-next-line no-undef
|
||||
const mock: JestMockFn<any, any> = jest.fn(
|
||||
(fetchUrl, fetchOptions) => {
|
||||
// Define DOITLIVE in the environment to test against the real full node
|
||||
// identified by `url` instead of using the mock
|
||||
if (process.env.DOITLIVE) {
|
||||
console.log(`Note: node-fetch mock is disabled, testing live against ${fetchUrl}`);
|
||||
return fetch(fetchUrl, fetchOptions);
|
||||
}
|
||||
|
||||
expect(mockRpc.length).toBeGreaterThanOrEqual(1);
|
||||
const [mockUrl, mockRequest, mockResponse] = mockRpc.shift();
|
||||
|
||||
|
@ -38,7 +47,6 @@ const mock: JestMockFn<any, any> = jest.fn(
|
|||
{
|
||||
jsonrpc: '2.0',
|
||||
method: 'invalid',
|
||||
params: ['invalid', 'params'],
|
||||
},
|
||||
mockRequest
|
||||
));
|
||||
|
|
|
@ -5,14 +5,7 @@ import {Account} from '../src/account';
|
|||
import {mockRpc} from './__mocks__/node-fetch';
|
||||
|
||||
const url = 'http://master.testnet.solana.com:8899';
|
||||
|
||||
// Define DOITLIVE in the environment to test against the real full node
|
||||
// identified by `url` instead of using the mock
|
||||
if (process.env.DOITLIVE) {
|
||||
console.log(`Note: node-fetch mock is disabled, testing live against ${url}`);
|
||||
} else {
|
||||
jest.mock('node-fetch');
|
||||
}
|
||||
//const url = 'http://localhost:8899';
|
||||
|
||||
const errorMessage = 'Invalid request';
|
||||
const errorResponse = {
|
||||
|
@ -22,6 +15,7 @@ const errorResponse = {
|
|||
result: undefined,
|
||||
};
|
||||
|
||||
|
||||
test('get balance', async () => {
|
||||
const account = new Account();
|
||||
const connection = new Connection(url);
|
||||
|
@ -109,7 +103,7 @@ test('get last Id', async () => {
|
|||
},
|
||||
{
|
||||
error: null,
|
||||
result: '1111111111111111111111111111111111111111111111',
|
||||
result: '2BjEqiiT43J6XskiHdz7aoocjPeWkCPiKD72SiFQsrA2',
|
||||
}
|
||||
]
|
||||
);
|
||||
|
@ -200,35 +194,125 @@ test('request airdrop - error', () => {
|
|||
.rejects.toThrow(errorMessage);
|
||||
});
|
||||
|
||||
test('send transaction - error', () => {
|
||||
const secretKey = Buffer.from([
|
||||
153, 218, 149, 89, 225, 94, 145, 62, 233, 171, 46, 83, 227,
|
||||
223, 173, 87, 93, 163, 59, 73, 190, 17, 37, 187, 146, 46, 51,
|
||||
73, 79, 73, 136, 40, 27, 47, 73, 9, 110, 62, 93, 189, 15, 207,
|
||||
169, 192, 192, 205, 146, 217, 171, 59, 33, 84, 75, 52, 213, 221,
|
||||
74, 101, 217, 139, 135, 139, 153, 34
|
||||
]);
|
||||
const account = new Account(secretKey);
|
||||
test('transaction', async () => {
|
||||
const accountFrom = new Account();
|
||||
const accountTo = new Account();
|
||||
const connection = new Connection(url);
|
||||
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'sendTransaction',
|
||||
params: [[
|
||||
78, 52, 48, 146, 162, 213, 83, 169, 128, 10, 82, 26, 145, 238,
|
||||
1, 130, 16, 44, 249, 99, 121, 55, 217, 72, 77, 41, 73, 227, 5,
|
||||
15, 125, 212, 186, 157, 182, 100, 232, 232, 39, 84, 5, 121, 172,
|
||||
137, 177, 248, 188, 224, 196, 102, 204, 43, 128, 243, 170, 157,
|
||||
134, 216, 209, 8, 211, 209, 44, 1
|
||||
]],
|
||||
method: 'requestAirdrop',
|
||||
params: [accountFrom.publicKey, 12],
|
||||
},
|
||||
errorResponse,
|
||||
{
|
||||
error: null,
|
||||
result: true,
|
||||
}
|
||||
]);
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getBalance',
|
||||
params: [accountFrom.publicKey],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: 12,
|
||||
}
|
||||
]);
|
||||
await connection.requestAirdrop(accountFrom.publicKey, 12);
|
||||
expect(await connection.getBalance(accountFrom.publicKey)).toBe(12);
|
||||
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'requestAirdrop',
|
||||
params: [accountTo.publicKey, 21],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: true,
|
||||
}
|
||||
]);
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getBalance',
|
||||
params: [accountTo.publicKey],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: 21,
|
||||
}
|
||||
]);
|
||||
await connection.requestAirdrop(accountTo.publicKey, 21);
|
||||
expect(await connection.getBalance(accountTo.publicKey)).toBe(21);
|
||||
|
||||
expect(connection.sendTokens(account, account.publicKey, 123))
|
||||
.rejects.toThrow(errorMessage);
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getLastId',
|
||||
params: [],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: '2BjEqiiT43J6XskiHdz7aoocjPeWkCPiKD72SiFQsrA2',
|
||||
}
|
||||
]
|
||||
);
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'sendTransaction',
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
|
||||
}
|
||||
]
|
||||
);
|
||||
const signature = await connection.sendTokens(accountFrom, accountTo.publicKey, 10);
|
||||
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'confirmTransaction',
|
||||
params: [
|
||||
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk'
|
||||
],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: true,
|
||||
}
|
||||
]
|
||||
);
|
||||
expect(connection.confirmTransaction(signature)).resolves.toBe(true);
|
||||
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getBalance',
|
||||
params: [accountFrom.publicKey],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: 2,
|
||||
}
|
||||
]);
|
||||
expect(await connection.getBalance(accountFrom.publicKey)).toBe(2);
|
||||
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getBalance',
|
||||
params: [accountTo.publicKey],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: 31,
|
||||
}
|
||||
]);
|
||||
expect(await connection.getBalance(accountTo.publicKey)).toBe(31);
|
||||
});
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue