Add support for built-in contracts
This commit is contained in:
parent
7dad281f69
commit
a96ab72e8e
|
@ -37,7 +37,8 @@
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "npm run lint --fix",
|
"lint:fix": "npm run lint --fix",
|
||||||
"lint:watch": "watch 'npm run lint:fix' . --wait=1 --ignoreDirectoryPattern=/doc/",
|
"lint:watch": "watch 'npm run lint:fix' . --wait=1 --ignoreDirectoryPattern=/doc/",
|
||||||
"prepublish": "npm run clean && npm run test && npm run flow && npm run lint && npm run doc && npm run build"
|
"ok": "npm run lint && npm run flow && npm run test && npm run doc",
|
||||||
|
"prepublish": "npm run clean && npm run ok && npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import {Transaction} from './transaction';
|
||||||
|
import type {PublicKey} from './account';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a condition that is met by executing a `applySignature()`
|
||||||
|
* transaction
|
||||||
|
*/
|
||||||
|
export type SignatureCondition = {
|
||||||
|
type: 'signature';
|
||||||
|
from: PublicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a condition that is met by executing a `applyTimestamp()`
|
||||||
|
* transaction
|
||||||
|
*/
|
||||||
|
export type TimeStampCondition = {
|
||||||
|
type: 'timestamp';
|
||||||
|
from: PublicKey;
|
||||||
|
when: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a payment to a given public key
|
||||||
|
*/
|
||||||
|
export type Payment = {
|
||||||
|
amount: number;
|
||||||
|
to: PublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditions that can unlock a payment
|
||||||
|
*/
|
||||||
|
export type BudgetCondition = SignatureCondition | TimeStampCondition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function serializePayment(payment: Payment): Buffer {
|
||||||
|
const toData = Transaction.serializePublicKey(payment.to);
|
||||||
|
const userdata = Buffer.alloc(8 + toData.length);
|
||||||
|
userdata.writeUInt32LE(payment.amount, 0);
|
||||||
|
toData.copy(userdata, 8);
|
||||||
|
return userdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function serializeDate(
|
||||||
|
when: Date
|
||||||
|
): Buffer {
|
||||||
|
const userdata = Buffer.alloc(8 + 20);
|
||||||
|
userdata.writeUInt32LE(20, 0); // size of timestamp as u64
|
||||||
|
|
||||||
|
function iso(date) {
|
||||||
|
function pad(number) {
|
||||||
|
if (number < 10) {
|
||||||
|
return '0' + number;
|
||||||
|
}
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.getUTCFullYear() +
|
||||||
|
'-' + pad(date.getUTCMonth() + 1) +
|
||||||
|
'-' + pad(date.getUTCDate()) +
|
||||||
|
'T' + pad(date.getUTCHours()) +
|
||||||
|
':' + pad(date.getUTCMinutes()) +
|
||||||
|
':' + pad(date.getUTCSeconds()) +
|
||||||
|
'Z';
|
||||||
|
}
|
||||||
|
userdata.write(iso(when), 8);
|
||||||
|
return userdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function serializeCondition(condition: BudgetCondition) {
|
||||||
|
switch(condition.type) {
|
||||||
|
case 'timestamp':
|
||||||
|
{
|
||||||
|
const date = serializeDate(condition.when);
|
||||||
|
const from = Transaction.serializePublicKey(condition.from);
|
||||||
|
|
||||||
|
const userdata = Buffer.alloc(4 + date.length + from.length);
|
||||||
|
userdata.writeUInt32LE(0, 0); // Condition enum = Timestamp
|
||||||
|
date.copy(userdata, 4);
|
||||||
|
from.copy(userdata, 4 + date.length);
|
||||||
|
return userdata;
|
||||||
|
}
|
||||||
|
case 'signature':
|
||||||
|
{
|
||||||
|
const from = Transaction.serializePublicKey(condition.from);
|
||||||
|
|
||||||
|
const userdata = Buffer.alloc(4 + from.length);
|
||||||
|
userdata.writeUInt32LE(1, 0); // Condition enum = Signature
|
||||||
|
from.copy(userdata, 4);
|
||||||
|
return userdata;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown condition type: ${condition.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory class for transactions to interact with the Budget contract
|
||||||
|
*/
|
||||||
|
export class BudgetContract {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public key that identifies the Budget Contract
|
||||||
|
*/
|
||||||
|
static get contractId(): PublicKey {
|
||||||
|
return '4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a timestamp condition
|
||||||
|
*/
|
||||||
|
static timestampCondition(from: PublicKey, when: Date) : TimeStampCondition {
|
||||||
|
return {
|
||||||
|
type: 'timestamp',
|
||||||
|
from,
|
||||||
|
when,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a signature condition
|
||||||
|
*/
|
||||||
|
static signatureCondition(from: PublicKey) : SignatureCondition {
|
||||||
|
return {
|
||||||
|
type: 'signature',
|
||||||
|
from,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a transaction that transfer tokens once a set of conditions are
|
||||||
|
* met
|
||||||
|
*/
|
||||||
|
static pay(
|
||||||
|
from: PublicKey,
|
||||||
|
contract: PublicKey,
|
||||||
|
to: PublicKey,
|
||||||
|
amount: number,
|
||||||
|
...conditions: Array<BudgetCondition>
|
||||||
|
): Transaction {
|
||||||
|
|
||||||
|
const userdata = Buffer.alloc(1024);
|
||||||
|
let pos = 0;
|
||||||
|
userdata.writeUInt32LE(0, pos); // NewContract instruction
|
||||||
|
pos += 4;
|
||||||
|
|
||||||
|
userdata.writeUInt32LE(amount, pos); // Contract.tokens
|
||||||
|
pos += 8;
|
||||||
|
|
||||||
|
userdata.writeUInt32LE(0, pos); // Contract.plan = Budget
|
||||||
|
pos += 4;
|
||||||
|
|
||||||
|
switch (conditions.length) {
|
||||||
|
case 0:
|
||||||
|
userdata.writeUInt32LE(0, pos); // Budget enum = Pay
|
||||||
|
pos += 4;
|
||||||
|
|
||||||
|
{
|
||||||
|
const payment = serializePayment({amount, to});
|
||||||
|
payment.copy(userdata, pos);
|
||||||
|
pos += payment.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Transaction({
|
||||||
|
fee: 0,
|
||||||
|
keys: [from, to],
|
||||||
|
contractId: this.contractId,
|
||||||
|
userdata: userdata.slice(0, pos),
|
||||||
|
});
|
||||||
|
case 1:
|
||||||
|
userdata.writeUInt32LE(1, pos); // Budget enum = After
|
||||||
|
pos += 4;
|
||||||
|
{
|
||||||
|
const condition = conditions[0];
|
||||||
|
|
||||||
|
const conditionData = serializeCondition(condition);
|
||||||
|
conditionData.copy(userdata, pos);
|
||||||
|
pos += conditionData.length;
|
||||||
|
|
||||||
|
const paymentData = serializePayment({amount, to});
|
||||||
|
paymentData.copy(userdata, pos);
|
||||||
|
pos += paymentData.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Transaction({
|
||||||
|
fee: 0,
|
||||||
|
keys: [from, contract],
|
||||||
|
contractId: this.contractId,
|
||||||
|
userdata: userdata.slice(0, pos),
|
||||||
|
});
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
userdata.writeUInt32LE(2, pos); // Budget enum = Ok
|
||||||
|
pos += 4;
|
||||||
|
|
||||||
|
for (let condition of conditions) {
|
||||||
|
const conditionData = serializeCondition(condition);
|
||||||
|
conditionData.copy(userdata, pos);
|
||||||
|
pos += conditionData.length;
|
||||||
|
|
||||||
|
const paymentData = serializePayment({amount, to});
|
||||||
|
paymentData.copy(userdata, pos);
|
||||||
|
pos += paymentData.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Transaction({
|
||||||
|
fee: 0,
|
||||||
|
keys: [from, to],
|
||||||
|
contractId: this.contractId,
|
||||||
|
userdata: userdata.slice(0, pos),
|
||||||
|
});
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`A maximum of two conditions are support: ${conditions.length} provided`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static applyTimestamp(from: PublicKey, contract: PublicKey, to: PublicKey, when: Date): Transaction {
|
||||||
|
const whenData = serializeDate(when);
|
||||||
|
const userdata = Buffer.alloc(4 + whenData.length);
|
||||||
|
|
||||||
|
userdata.writeUInt32LE(1, 0); // ApplyTimestamp instruction
|
||||||
|
whenData.copy(userdata, 4);
|
||||||
|
|
||||||
|
return new Transaction({
|
||||||
|
fee: 0,
|
||||||
|
keys: [from, contract, to],
|
||||||
|
contractId: this.contractId,
|
||||||
|
userdata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static applySignature(from: PublicKey, contract: PublicKey, to: PublicKey): Transaction {
|
||||||
|
const userdata = Buffer.alloc(4);
|
||||||
|
userdata.writeUInt32LE(2, 0); // ApplySignature instruction
|
||||||
|
|
||||||
|
return new Transaction({
|
||||||
|
fee: 0,
|
||||||
|
keys: [from, contract, to],
|
||||||
|
contractId: this.contractId,
|
||||||
|
userdata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
|
import {Transaction} from './transaction';
|
||||||
|
import type {PublicKey} from './account';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory class for transactions to interact with the System contract
|
||||||
|
*/
|
||||||
|
export class SystemContract {
|
||||||
|
/**
|
||||||
|
* Public key that identifies the System Contract
|
||||||
|
*/
|
||||||
|
static get contractId(): PublicKey {
|
||||||
|
return '11111111111111111111111111111111';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a Transaction that creates a new account
|
||||||
|
*/
|
||||||
|
static createAccount(
|
||||||
|
from: PublicKey,
|
||||||
|
newAccount: PublicKey,
|
||||||
|
tokens: number,
|
||||||
|
space: number,
|
||||||
|
contractId: ?PublicKey
|
||||||
|
): Transaction {
|
||||||
|
const userdata = Buffer.alloc(4 + 8 + 8 + 1 + 32);
|
||||||
|
let pos = 0;
|
||||||
|
|
||||||
|
userdata.writeUInt32LE(0, pos); // Create Account instruction
|
||||||
|
pos += 4;
|
||||||
|
|
||||||
|
userdata.writeUInt32LE(tokens, pos); // tokens as i64
|
||||||
|
pos += 8;
|
||||||
|
|
||||||
|
userdata.writeUInt32LE(space, pos); // space as u64
|
||||||
|
pos += 8;
|
||||||
|
|
||||||
|
if (contractId) {
|
||||||
|
userdata.writeUInt8(1, pos); // 'Some'
|
||||||
|
pos += 1;
|
||||||
|
|
||||||
|
const contractIdBytes = Transaction.serializePublicKey(contractId);
|
||||||
|
contractIdBytes.copy(userdata, pos);
|
||||||
|
pos += 32;
|
||||||
|
} else {
|
||||||
|
userdata.writeUInt8(0, pos); // 'None'
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(pos <= userdata.length);
|
||||||
|
|
||||||
|
return new Transaction({
|
||||||
|
fee: 0,
|
||||||
|
keys: [from, newAccount],
|
||||||
|
contractId: SystemContract.contractId,
|
||||||
|
userdata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a Transaction that moves tokens from one account to another
|
||||||
|
*/
|
||||||
|
static move(from: PublicKey, to: PublicKey, amount: number): Transaction {
|
||||||
|
const userdata = Buffer.alloc(4 + 8);
|
||||||
|
let pos = 0;
|
||||||
|
userdata.writeUInt32LE(2, pos); // Move instruction
|
||||||
|
pos += 4;
|
||||||
|
|
||||||
|
userdata.writeUInt32LE(amount, pos); // amount as u64
|
||||||
|
pos += 8;
|
||||||
|
|
||||||
|
assert(pos === userdata.length);
|
||||||
|
|
||||||
|
return new Transaction({
|
||||||
|
fee: 0,
|
||||||
|
keys: [from, to],
|
||||||
|
contractId: SystemContract.contractId,
|
||||||
|
userdata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a Transaction that assigns an account to a contract id
|
||||||
|
*/
|
||||||
|
static assign(from: PublicKey, contractId: PublicKey): Transaction {
|
||||||
|
const userdata = Buffer.alloc(4 + 32);
|
||||||
|
let pos = 0;
|
||||||
|
|
||||||
|
userdata.writeUInt32LE(1, pos); // Assign instruction
|
||||||
|
pos += 4;
|
||||||
|
|
||||||
|
const contractIdBytes = Transaction.serializePublicKey(contractId);
|
||||||
|
contractIdBytes.copy(userdata, pos);
|
||||||
|
pos += contractIdBytes.length;
|
||||||
|
|
||||||
|
assert(pos === userdata.length);
|
||||||
|
|
||||||
|
return new Transaction({
|
||||||
|
fee: 0,
|
||||||
|
keys: [from],
|
||||||
|
contractId: SystemContract.contractId,
|
||||||
|
userdata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,15 +6,6 @@ import bs58 from 'bs58';
|
||||||
|
|
||||||
import type {Account, PublicKey} from './account';
|
import type {Account, PublicKey} from './account';
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function bs58DecodePublicKey(key: PublicKey): Buffer {
|
|
||||||
const keyBytes = Buffer.from(bs58.decode(key));
|
|
||||||
assert(keyBytes.length === 32);
|
|
||||||
return keyBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {string} TransactionSignature
|
* @typedef {string} TransactionSignature
|
||||||
*/
|
*/
|
||||||
|
@ -25,6 +16,17 @@ export type TransactionSignature = string;
|
||||||
*/
|
*/
|
||||||
export type TransactionId = string;
|
export type TransactionId = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of Transaction object fields that may be initialized at construction
|
||||||
|
*/
|
||||||
|
type TransactionCtorFields = {|
|
||||||
|
signature?: Buffer;
|
||||||
|
keys?: Array<PublicKey>;
|
||||||
|
contractId?: PublicKey;
|
||||||
|
fee?: number;
|
||||||
|
userdata?: Buffer;
|
||||||
|
|};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mirrors the Transaction struct in src/transaction.rs
|
* Mirrors the Transaction struct in src/transaction.rs
|
||||||
*/
|
*/
|
||||||
|
@ -41,6 +43,11 @@ export class Transaction {
|
||||||
*/
|
*/
|
||||||
keys: Array<PublicKey> = [];
|
keys: Array<PublicKey> = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contract Id to execute
|
||||||
|
*/
|
||||||
|
contractId: ?PublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A recent transaction id. Must be populated by the caller
|
* A recent transaction id. Must be populated by the caller
|
||||||
*/
|
*/
|
||||||
|
@ -56,6 +63,10 @@ export class Transaction {
|
||||||
*/
|
*/
|
||||||
userdata: Buffer = Buffer.alloc(0);
|
userdata: Buffer = Buffer.alloc(0);
|
||||||
|
|
||||||
|
constructor(opts?: TransactionCtorFields) {
|
||||||
|
opts && Object.assign(this, opts);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
@ -74,11 +85,18 @@ export class Transaction {
|
||||||
transactionData.writeUInt32LE(this.keys.length, pos); // u64
|
transactionData.writeUInt32LE(this.keys.length, pos); // u64
|
||||||
pos += 8;
|
pos += 8;
|
||||||
for (let key of this.keys) {
|
for (let key of this.keys) {
|
||||||
const keyBytes = bs58DecodePublicKey(key);
|
const keyBytes = Transaction.serializePublicKey(key);
|
||||||
keyBytes.copy(transactionData, pos);
|
keyBytes.copy(transactionData, pos);
|
||||||
pos += 32;
|
pos += 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// serialize `this.contractId`
|
||||||
|
if (this.contractId) {
|
||||||
|
const keyBytes = Transaction.serializePublicKey(this.contractId);
|
||||||
|
keyBytes.copy(transactionData, pos);
|
||||||
|
}
|
||||||
|
pos += 32;
|
||||||
|
|
||||||
// serialize `this.lastId`
|
// serialize `this.lastId`
|
||||||
{
|
{
|
||||||
const lastIdBytes = Buffer.from(bs58.decode(lastId));
|
const lastIdBytes = Buffer.from(bs58.decode(lastId));
|
||||||
|
@ -93,6 +111,8 @@ export class Transaction {
|
||||||
|
|
||||||
// serialize `this.userdata`
|
// serialize `this.userdata`
|
||||||
if (this.userdata.length > 0) {
|
if (this.userdata.length > 0) {
|
||||||
|
transactionData.writeUInt32LE(this.userdata.length, pos); // u64
|
||||||
|
pos += 8;
|
||||||
this.userdata.copy(transactionData, pos);
|
this.userdata.copy(transactionData, pos);
|
||||||
pos += this.userdata.length;
|
pos += this.userdata.length;
|
||||||
}
|
}
|
||||||
|
@ -131,28 +151,14 @@ export class Transaction {
|
||||||
transactionData.copy(wireTransaction, signature.length);
|
transactionData.copy(wireTransaction, signature.length);
|
||||||
return wireTransaction;
|
return wireTransaction;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Transaction that immediately transfers tokens
|
* Serializes a public key into the wire format
|
||||||
*/
|
*/
|
||||||
export class TransferTokensTransaction extends Transaction {
|
static serializePublicKey(key: PublicKey): Buffer {
|
||||||
constructor(from: PublicKey, to: PublicKey, amount: number) {
|
const data = Buffer.from(bs58.decode(key));
|
||||||
super();
|
assert(data.length === 32);
|
||||||
|
return data;
|
||||||
// Forge a simple Budget Pay contract into `userdata`
|
|
||||||
// TODO: Clean this up
|
|
||||||
const userdata = Buffer.alloc(68); // 68 = serialized size of Budget enum
|
|
||||||
userdata.writeUInt32LE(60, 0);
|
|
||||||
userdata.writeUInt32LE(amount, 12); // u64
|
|
||||||
userdata.writeUInt32LE(amount, 28); // u64
|
|
||||||
const toData = bs58DecodePublicKey(to);
|
|
||||||
toData.copy(userdata, 36);
|
|
||||||
|
|
||||||
Object.assign(this, {
|
|
||||||
fee: 0,
|
|
||||||
keys: [from, to],
|
|
||||||
userdata
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import {Account} from '../src/account';
|
||||||
|
import {BudgetContract} from '../src/budget-contract';
|
||||||
|
|
||||||
|
test('pay', () => {
|
||||||
|
const from = new Account();
|
||||||
|
const contract = new Account();
|
||||||
|
const to = new Account();
|
||||||
|
let transaction;
|
||||||
|
|
||||||
|
transaction = BudgetContract.pay(
|
||||||
|
from.publicKey,
|
||||||
|
contract.publicKey,
|
||||||
|
to.publicKey,
|
||||||
|
123,
|
||||||
|
);
|
||||||
|
console.log('Pay:', transaction);
|
||||||
|
|
||||||
|
transaction = BudgetContract.pay(
|
||||||
|
from.publicKey,
|
||||||
|
contract.publicKey,
|
||||||
|
to.publicKey,
|
||||||
|
123,
|
||||||
|
BudgetContract.signatureCondition(from.publicKey),
|
||||||
|
);
|
||||||
|
console.log('After:', transaction);
|
||||||
|
|
||||||
|
transaction = BudgetContract.pay(
|
||||||
|
from.publicKey,
|
||||||
|
contract.publicKey,
|
||||||
|
to.publicKey,
|
||||||
|
123,
|
||||||
|
BudgetContract.signatureCondition(from.publicKey),
|
||||||
|
BudgetContract.timestampCondition(from.publicKey, new Date()),
|
||||||
|
);
|
||||||
|
console.log('Or:', transaction);
|
||||||
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import {Account} from '../src/account';
|
import {Account} from '../src/account';
|
||||||
import {Connection} from '../src/connection';
|
import {Connection} from '../src/connection';
|
||||||
import {TransferTokensTransaction} from '../src/transaction';
|
import {SystemContract} from '../src/system-contract';
|
||||||
import {mockRpc} from './__mocks__/node-fetch';
|
import {mockRpc} from './__mocks__/node-fetch';
|
||||||
|
|
||||||
const url = 'http://testnet.solana.com:8899';
|
const url = 'http://testnet.solana.com:8899';
|
||||||
|
@ -274,7 +274,7 @@ test('transaction', async () => {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const transaction = new TransferTokensTransaction(
|
const transaction = SystemContract.move(
|
||||||
accountFrom.publicKey,
|
accountFrom.publicKey,
|
||||||
accountTo.publicKey,
|
accountTo.publicKey,
|
||||||
10
|
10
|
||||||
|
|
Loading…
Reference in New Issue