feat: wrap public key in a class

This commit is contained in:
Michael Vines 2018-09-30 18:42:45 -07:00
parent 2c3208090c
commit ca6965f8c2
15 changed files with 180 additions and 95 deletions

View File

@ -7,4 +7,4 @@ const solanaWeb3 = require('..');
//const solanaWeb3 = require('@solana/web3.js');
const account = new solanaWeb3.Account();
console.log(account.publicKey);
console.log(account.publicKey.toString());

4
web3.js/flow-typed/bn.js.js vendored Normal file
View File

@ -0,0 +1,4 @@
declare module 'bn.js' {
// TODO: Fill in types
declare module.exports: any;
}

View File

@ -13,11 +13,20 @@
declare module '@solana/web3.js' {
declare export type PublicKey = string;
// === src/publickey.js ===
declare export class PublicKey {
constructor(number: string | Buffer | Array<number>): PublicKey;
static isPublicKey(o: Object): boolean;
equals(publickey: PublicKey): boolean;
toBase58(): string;
toBuffer(): Buffer;
}
// === src/account.js ===
declare export class Account {
constructor(secretKey: ?Buffer): Account;
publicKey: PublicKey;
secretKey: PublicKey;
secretKey: Buffer;
}
// === src/budget-program.js ===
@ -84,6 +93,5 @@ declare module '@solana/web3.js' {
constructor(opts?: TransactionCtorFields): Transaction;
sign(from: Account): void;
serialize(): Buffer;
static serializePublicKey(key: PublicKey): Buffer;
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@solana/web3.js",
"version": "0.0.7",
"version": "0.0.0-development",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -2261,8 +2261,7 @@
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"dev": true
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
},
"boolbase": {
"version": "1.0.0",

View File

@ -43,6 +43,7 @@
},
"dependencies": {
"babel-runtime": "^6.26.0",
"bn.js": "^4.11.8",
"bs58": "^4.0.1",
"jayson": "^2.0.6",
"node-fetch": "^2.2.0",

View File

@ -1,14 +1,8 @@
// @flow
import nacl from 'tweetnacl';
import bs58 from 'bs58';
import type {KeyPair} from 'tweetnacl';
/**
* Base 58 encoded public key
*
* @typedef {string} PublicKey
*/
export type PublicKey = string;
import {PublicKey} from './publickey';
/**
* An account key pair (public and secret keys).
@ -36,7 +30,7 @@ export class Account {
* The public key for this account
*/
get publicKey(): PublicKey {
return bs58.encode(this._keypair.publicKey);
return new PublicKey(this._keypair.publicKey);
}
/**

View File

@ -1,7 +1,7 @@
// @flow
import {Transaction} from './transaction';
import type {PublicKey} from './account';
import {PublicKey} from './publickey';
/**
* Represents a condition that is met by executing a `applySignature()`
@ -54,7 +54,7 @@ export type BudgetCondition = SignatureCondition | TimestampCondition;
* @private
*/
function serializePayment(payment: Payment): Buffer {
const toData = Transaction.serializePublicKey(payment.to);
const toData = payment.to.toBuffer();
const userdata = Buffer.alloc(8 + toData.length);
userdata.writeUInt32LE(payment.amount, 0);
toData.copy(userdata, 8);
@ -98,7 +98,7 @@ function serializeCondition(condition: BudgetCondition) {
case 'timestamp':
{
const date = serializeDate(condition.when);
const from = Transaction.serializePublicKey(condition.from);
const from = condition.from.toBuffer();
const userdata = Buffer.alloc(4 + date.length + from.length);
userdata.writeUInt32LE(0, 0); // Condition enum = Timestamp
@ -108,7 +108,7 @@ function serializeCondition(condition: BudgetCondition) {
}
case 'signature':
{
const from = Transaction.serializePublicKey(condition.from);
const from = condition.from.toBuffer();
const userdata = Buffer.alloc(4 + from.length);
userdata.writeUInt32LE(1, 0); // Condition enum = Signature
@ -130,7 +130,7 @@ export class BudgetProgram {
* Public key that identifies the Budget program
*/
static get programId(): PublicKey {
return '4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM';
return new PublicKey('0x100000000000000000000000000000000000000000000000000000000000000');
}
/**

View File

@ -4,10 +4,10 @@ import assert from 'assert';
import fetch from 'node-fetch';
import jayson from 'jayson/lib/client/browser';
import {struct} from 'superstruct';
import bs58 from 'bs58';
import {Transaction} from './transaction';
import type {Account, PublicKey} from './account';
import {PublicKey} from './publickey';
import type {Account} from './account';
import type {TransactionSignature, TransactionId} from './transaction';
type RpcRequest = (methodName: string, args: Array<any>) => any;
@ -174,7 +174,7 @@ export class Connection {
async getBalance(publicKey: PublicKey): Promise<number> {
const unsafeRes = await this._rpcRequest(
'getBalance',
[publicKey]
[publicKey.toBase58()]
);
const res = GetBalanceRpcResult(unsafeRes);
if (res.error) {
@ -190,7 +190,7 @@ export class Connection {
async getAccountInfo(publicKey: PublicKey): Promise<AccountInfo> {
const unsafeRes = await this._rpcRequest(
'getAccountInfo',
[publicKey]
[publicKey.toBase58()]
);
const res = GetAccountInfoRpcResult(unsafeRes);
if (res.error) {
@ -202,7 +202,7 @@ export class Connection {
return {
tokens: result.tokens,
programId: bs58.encode(result.program_id),
programId: new PublicKey(result.program_id),
userdata: Buffer.from(result.userdata),
};
}
@ -280,7 +280,7 @@ export class Connection {
* Request an allocation of tokens to the specified account
*/
async requestAirdrop(to: PublicKey, amount: number): Promise<TransactionSignature> {
const unsafeRes = await this._rpcRequest('requestAirdrop', [to, amount]);
const unsafeRes = await this._rpcRequest('requestAirdrop', [to.toBase58(), amount]);
const res = RequestAirdropRpcResult(unsafeRes);
if (res.error) {
throw new Error(res.error.message);

View File

@ -1,6 +1,7 @@
// @flow
export {Account} from './account';
export {Connection} from './connection';
export {Transaction} from './transaction';
export {SystemProgram} from './system-program';
export {BudgetProgram} from './budget-program';
export {Connection} from './connection';
export {PublicKey} from './publickey';
export {SystemProgram} from './system-program';
export {Transaction} from './transaction';

70
web3.js/src/publickey.js Normal file
View File

@ -0,0 +1,70 @@
// @flow
import BN from 'bn.js';
import bs58 from 'bs58';
/**
* A public key
*/
export class PublicKey {
_bn: BN;
/**
* Create a new PublicKey object
*/
constructor(number: string | Buffer | Array<number>) {
let radix = 10;
if (typeof number === 'string' && number.startsWith('0x')) {
this._bn = new BN(number.substring(2), 16);
} else {
this._bn = new BN(number, radix);
}
if (this._bn.byteLength() > 32) {
throw new Error(`Invalid public key input`);
}
}
/**
* Checks if the provided object is a PublicKey
*/
static isPublicKey(o: Object): boolean {
return o instanceof PublicKey;
}
/**
* Checks if two publicKeys are equal
*/
equals(publicKey: PublicKey): boolean {
return this._bn.eq(publicKey._bn);
}
/**
* Return the base-58 representation of the public key
*/
toBase58(): string {
return bs58.encode(this.toBuffer());
}
/**
* Return the base-58 representation of the public key
*/
toBuffer(): Buffer {
const b = this._bn.toBuffer();
if (b.length === 32) {
return b;
}
const zeroPad = new Buffer(32);
b.copy(zeroPad, 32 - b.length);
return zeroPad;
}
/**
* Returns a string representation of the public key
*/
toString(): string {
return this.toBase58();
}
}

View File

@ -3,7 +3,7 @@
import assert from 'assert';
import {Transaction} from './transaction';
import type {PublicKey} from './account';
import {PublicKey} from './publickey';
/**
* Factory class for transactions to interact with the System program
@ -13,7 +13,7 @@ export class SystemProgram {
* Public key that identifies the System program
*/
static get programId(): PublicKey {
return '11111111111111111111111111111111';
return new PublicKey('0x000000000000000000000000000000000000000000000000000000000000000');
}
/**
@ -38,7 +38,7 @@ export class SystemProgram {
userdata.writeUInt32LE(space, pos); // space as u64
pos += 8;
const programIdBytes = Transaction.serializePublicKey(programId);
const programIdBytes = programId.toBuffer();
programIdBytes.copy(userdata, pos);
pos += 32;
@ -84,7 +84,7 @@ export class SystemProgram {
userdata.writeUInt32LE(1, pos); // Assign instruction
pos += 4;
const programIdBytes = Transaction.serializePublicKey(programId);
const programIdBytes = programId.toBuffer();
programIdBytes.copy(userdata, pos);
pos += programIdBytes.length;

View File

@ -4,7 +4,8 @@ import assert from 'assert';
import nacl from 'tweetnacl';
import bs58 from 'bs58';
import type {Account, PublicKey} from './account';
import type {Account} from './account';
import type {PublicKey} from './publickey';
/**
* @typedef {string} TransactionSignature
@ -92,14 +93,14 @@ export class Transaction {
transactionData.writeUInt32LE(this.keys.length, pos); // u64
pos += 8;
for (let key of this.keys) {
const keyBytes = Transaction.serializePublicKey(key);
const keyBytes = key.toBuffer();
keyBytes.copy(transactionData, pos);
pos += 32;
}
// serialize `this.programId`
if (this.programId) {
const keyBytes = Transaction.serializePublicKey(this.programId);
const keyBytes = this.programId.toBuffer();
keyBytes.copy(transactionData, pos);
}
pos += 32;
@ -158,14 +159,5 @@ export class Transaction {
transactionData.copy(wireTransaction, signature.length);
return wireTransaction;
}
/**
* Serializes a public key into the wire format
*/
static serializePublicKey(key: PublicKey): Buffer {
const data = Buffer.from(bs58.decode(key));
assert(data.length === 32);
return data;
}
}

View File

@ -1,10 +1,10 @@
// @flow
import {Account} from '../src/account';
import {PublicKey} from '../src/publickey';
test('generate new account', () => {
const account = new Account();
expect(account.publicKey.length).toBeGreaterThanOrEqual(43);
expect(account.publicKey.length).toBeLessThanOrEqual(44);
expect(PublicKey.isPublicKey(account.publicKey)).toBeTruthy();
expect(account.secretKey).toHaveLength(64);
});
@ -17,5 +17,5 @@ test('account from secret key', () => {
74, 101, 217, 139, 135, 139, 153, 34
]);
const account = new Account(secretKey);
expect(account.publicKey).toBe('2q7pyhPwAwZ3QMfZrnAbDhnh9mDUqycszcpf86VgQxhF');
expect(account.publicKey.toBase58()).toBe('2q7pyhPwAwZ3QMfZrnAbDhnh9mDUqycszcpf86VgQxhF');
});

View File

@ -26,7 +26,7 @@ test('get account info - error', () => {
url,
{
method: 'getAccountInfo',
params: [account.publicKey],
params: [account.publicKey.toBase58()],
},
errorResponse,
]);
@ -44,7 +44,7 @@ test('get balance', async () => {
url,
{
method: 'getBalance',
params: [account.publicKey],
params: [account.publicKey.toBase58()],
},
{
error: null,
@ -56,23 +56,6 @@ test('get balance', async () => {
expect(balance).toBeGreaterThanOrEqual(0);
});
test('get balance - error', () => {
const connection = new Connection(url);
const invalidPublicKey = 'not a public key';
mockRpc.push([
url,
{
method: 'getBalance',
params: [invalidPublicKey],
},
errorResponse,
]);
expect(connection.getBalance(invalidPublicKey))
.rejects.toThrow(errorMessage);
});
test('confirm transaction - error', () => {
const connection = new Connection(url);
@ -174,7 +157,7 @@ test('request airdrop', async () => {
url,
{
method: 'requestAirdrop',
params: [account.publicKey, 40],
params: [account.publicKey.toBase58(), 40],
},
{
error: null,
@ -185,7 +168,7 @@ test('request airdrop', async () => {
url,
{
method: 'requestAirdrop',
params: [account.publicKey, 2],
params: [account.publicKey.toBase58(), 2],
},
{
error: null,
@ -196,7 +179,7 @@ test('request airdrop', async () => {
url,
{
method: 'getBalance',
params: [account.publicKey],
params: [account.publicKey.toBase58()],
},
{
error: null,
@ -214,7 +197,7 @@ test('request airdrop', async () => {
url,
{
method: 'getAccountInfo',
params: [account.publicKey],
params: [account.publicKey.toBase58()],
},
{
error: null,
@ -226,31 +209,13 @@ test('request airdrop', async () => {
tokens: 42,
userdata: [],
}
}
]);
const accountInfo = await connection.getAccountInfo(account.publicKey);
expect(accountInfo.tokens).toBe(42);
expect(accountInfo.userdata).toHaveLength(0);
expect(accountInfo.programId).toBe(SystemProgram.programId);
});
test('request airdrop - error', () => {
const invalidPublicKey = 'not a public key';
const connection = new Connection(url);
mockRpc.push([
url,
{
method: 'requestAirdrop',
params: [invalidPublicKey, 1],
},
errorResponse
]);
expect(connection.requestAirdrop(invalidPublicKey, 1))
.rejects.toThrow(errorMessage);
expect(accountInfo.programId).toEqual(SystemProgram.programId);
});
test('transaction', async () => {
@ -262,7 +227,7 @@ test('transaction', async () => {
url,
{
method: 'requestAirdrop',
params: [accountFrom.publicKey, 12],
params: [accountFrom.publicKey.toBase58(), 12],
},
{
error: null,
@ -273,7 +238,7 @@ test('transaction', async () => {
url,
{
method: 'getBalance',
params: [accountFrom.publicKey],
params: [accountFrom.publicKey.toBase58()],
},
{
error: null,
@ -287,7 +252,7 @@ test('transaction', async () => {
url,
{
method: 'requestAirdrop',
params: [accountTo.publicKey, 21],
params: [accountTo.publicKey.toBase58(), 21],
},
{
error: null,
@ -298,7 +263,7 @@ test('transaction', async () => {
url,
{
method: 'getBalance',
params: [accountTo.publicKey],
params: [accountTo.publicKey.toBase58()],
},
{
error: null,
@ -375,7 +340,7 @@ test('transaction', async () => {
url,
{
method: 'getBalance',
params: [accountFrom.publicKey],
params: [accountFrom.publicKey.toBase58()],
},
{
error: null,
@ -388,7 +353,7 @@ test('transaction', async () => {
url,
{
method: 'getBalance',
params: [accountTo.publicKey],
params: [accountTo.publicKey.toBase58()],
},
{
error: null,

View File

@ -0,0 +1,51 @@
// @flow
import {PublicKey} from '../src/publickey';
test('invalid', () => {
expect(() => {
new PublicKey([
3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]);
}).toThrow();
expect(() => {
new PublicKey('0x300000000000000000000000000000000000000000000000000000000000000000000');
}).toThrow();
expect(() => {
new PublicKey('135693854574979916511997248057056142015550763280047535983739356259273198796800000');
}).toThrow();
});
test('equals', () => {
const arrayKey = new PublicKey([
3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]);
const hexKey = new PublicKey('0x300000000000000000000000000000000000000000000000000000000000000');
const decimalKey = new PublicKey('1356938545749799165119972480570561420155507632800475359837393562592731987968');
expect(arrayKey.equals(hexKey)).toBeTruthy();
expect(arrayKey.equals(decimalKey)).toBeTruthy();
});
test('isPublicKey', () => {
const key = new PublicKey('0x100000000000000000000000000000000000000000000000000000000000000');
expect(PublicKey.isPublicKey(key)).toBeTruthy();
expect(PublicKey.isPublicKey({})).toBeFalsy();
});
test('toBase58', () => {
const key = new PublicKey('0x300000000000000000000000000000000000000000000000000000000000000');
expect(key.toBase58()).toBe('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3');
});
test('toBuffer', () => {
const key = new PublicKey('0x300000000000000000000000000000000000000000000000000000000000000');
expect(key.toBuffer()).toHaveLength(32);
expect(key.toBase58()).toBe('CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3');
const key2 = new PublicKey('0x000000000000000000000000000000000000000000000000000000000000000');
expect(key2.toBuffer()).toHaveLength(32);
expect(key2.toBase58()).toBe('11111111111111111111111111111111');
});