diff --git a/web3.js/examples/budget-timestamp.js b/web3.js/examples/budget-timestamp.js index d34db5d3a6..1be85769c9 100644 --- a/web3.js/examples/budget-timestamp.js +++ b/web3.js/examples/budget-timestamp.js @@ -9,7 +9,6 @@ const solanaWeb3 = require('..'); const account1 = new solanaWeb3.Account(); const account2 = new solanaWeb3.Account(); -const contractFunds = new solanaWeb3.Account(); const contractState = new solanaWeb3.Account(); let url; @@ -23,28 +22,20 @@ function showBalance() { return Promise.all([ connection.getBalance(account1.publicKey), connection.getBalance(account2.publicKey), - connection.getBalance(contractFunds.publicKey), connection.getBalance(contractState.publicKey), - ]).then( - ([fromBalance, toBalance, contractFundsBalance, contractStateBalance]) => { - console.log( - `Account1: ${account1.publicKey} has a balance of ${fromBalance}`, - ); - console.log( - `Account2: ${account2.publicKey} has a balance of ${toBalance}`, - ); - console.log( - `Contract Funds: ${ - contractFunds.publicKey - } has a balance of ${contractFundsBalance}`, - ); - console.log( - `Contract State: ${ - contractState.publicKey - } has a balance of ${contractStateBalance}`, - ); - }, - ); + ]).then(([fromBalance, toBalance, contractStateBalance]) => { + console.log( + `Account1: ${account1.publicKey} has a balance of ${fromBalance}`, + ); + console.log( + `Account2: ${account2.publicKey} has a balance of ${toBalance}`, + ); + console.log( + `Contract State: ${ + contractState.publicKey + } has a balance of ${contractStateBalance}`, + ); + }); } function confirmTransaction(signature) { @@ -52,6 +43,10 @@ function confirmTransaction(signature) { return connection.getSignatureStatus(signature).then(confirmation => { if (confirmation && 'Ok' in confirmation) { console.log('Transaction confirmed'); + } else if (confirmation) { + throw new Error( + `Transaction was not confirmed (${JSON.stringify(confirmation.Err)})`, + ); } else { throw new Error(`Transaction was not confirmed (${confirmation})`); } @@ -68,36 +63,10 @@ function airDrop() { showBalance() .then(airDrop) .then(showBalance) - .then(() => { - console.log(`\n== Creating account for the contract funds`); - const transaction = solanaWeb3.SystemProgram.createAccount( - account1.publicKey, - contractFunds.publicKey, - 50, // number of lamports to transfer - 0, - solanaWeb3.BudgetProgram.programId, - ); - return connection.sendTransaction(transaction, account1); - }) - .then(confirmTransaction) - .then(showBalance) - .then(() => { - console.log(`\n== Creating account for the contract state`); - const transaction = solanaWeb3.SystemProgram.createAccount( - account1.publicKey, - contractState.publicKey, - 1, // account1 pays 1 lamport to hold the contract state - solanaWeb3.BudgetProgram.space, - solanaWeb3.BudgetProgram.programId, - ); - return connection.sendTransaction(transaction, account1); - }) - .then(confirmTransaction) - .then(showBalance) .then(() => { console.log(`\n== Initializing contract`); const transaction = solanaWeb3.BudgetProgram.pay( - contractFunds.publicKey, + account1.publicKey, contractState.publicKey, account2.publicKey, 50, @@ -106,7 +75,11 @@ showBalance() new Date('2050'), ), ); - return connection.sendTransaction(transaction, contractFunds); + return solanaWeb3.sendAndConfirmTransaction( + connection, + transaction, + account1, + ); }) .then(confirmTransaction) .then(showBalance) @@ -118,7 +91,11 @@ showBalance() account2.publicKey, new Date('2050'), ); - return connection.sendTransaction(transaction, account1); + return solanaWeb3.sendAndConfirmTransaction( + connection, + transaction, + account1, + ); }) .then(confirmTransaction) .then(showBalance) diff --git a/web3.js/examples/budget-two-approvers.js b/web3.js/examples/budget-two-approvers.js index 7944b2b2a7..8ea9ad3538 100644 --- a/web3.js/examples/budget-two-approvers.js +++ b/web3.js/examples/budget-two-approvers.js @@ -8,7 +8,6 @@ const solanaWeb3 = require('..'); const account1 = new solanaWeb3.Account(); const account2 = new solanaWeb3.Account(); -const contractFunds = new solanaWeb3.Account(); const contractState = new solanaWeb3.Account(); const approver1 = new solanaWeb3.Account(); @@ -24,37 +23,34 @@ function showBalance() { return Promise.all([ connection.getBalance(account1.publicKey), connection.getBalance(account2.publicKey), - connection.getBalance(contractFunds.publicKey), connection.getBalance(contractState.publicKey), - ]).then( - ([fromBalance, toBalance, contractFundsBalance, contractStateBalance]) => { - console.log( - `Account1: ${account1.publicKey} has a balance of ${fromBalance}`, - ); - console.log( - `Account2: ${account2.publicKey} has a balance of ${toBalance}`, - ); - console.log( - `Contract Funds: ${ - contractFunds.publicKey - } has a balance of ${contractFundsBalance}`, - ); - console.log( - `Contract State: ${ - contractState.publicKey - } has a balance of ${contractStateBalance}`, - ); - }, - ); + ]).then(([fromBalance, toBalance, contractStateBalance]) => { + console.log( + `Account1: ${account1.publicKey} has a balance of ${fromBalance}`, + ); + console.log( + `Account2: ${account2.publicKey} has a balance of ${toBalance}`, + ); + console.log( + `Contract State: ${ + contractState.publicKey + } has a balance of ${contractStateBalance}`, + ); + }); } function confirmTransaction(signature) { console.log('Confirming transaction:', signature); return connection.getSignatureStatus(signature).then(confirmation => { - if (confirmation !== 'Confirmed') { + if (confirmation && 'Ok' in confirmation) { + console.log('Transaction confirmed'); + } else if (confirmation) { + throw new Error( + `Transaction was not confirmed (${JSON.stringify(confirmation.Err)})`, + ); + } else { throw new Error(`Transaction was not confirmed (${confirmation})`); } - console.log('Transaction confirmed'); }); } @@ -88,43 +84,21 @@ showBalance() }) .then(confirmTransaction) .then(showBalance) - .then(() => { - console.log(`\n== Creating account for the contract funds`); - const transaction = solanaWeb3.SystemProgram.createAccount( - account1.publicKey, - contractFunds.publicKey, - 50, // number of lamports to transfer - 0, - solanaWeb3.BudgetProgram.programId, - ); - return connection.sendTransaction(transaction, account1); - }) - .then(confirmTransaction) - .then(showBalance) - .then(() => { - console.log(`\n== Creating account for the contract state`); - const transaction = solanaWeb3.SystemProgram.createAccount( - account1.publicKey, - contractState.publicKey, - 1, // account1 pays 1 lamport to hold the contract state - solanaWeb3.BudgetProgram.space, - solanaWeb3.BudgetProgram.programId, - ); - return connection.sendTransaction(transaction, account1); - }) - .then(confirmTransaction) - .then(showBalance) .then(() => { console.log(`\n== Initializing contract`); const transaction = solanaWeb3.BudgetProgram.payOnBoth( - contractFunds.publicKey, + account1.publicKey, contractState.publicKey, account2.publicKey, 50, solanaWeb3.BudgetProgram.signatureCondition(approver1.publicKey), solanaWeb3.BudgetProgram.signatureCondition(approver2.publicKey), ); - return connection.sendTransaction(transaction, contractFunds); + return solanaWeb3.sendAndConfirmTransaction( + connection, + transaction, + account1, + ); }) .then(confirmTransaction) .then(showBalance) @@ -135,7 +109,11 @@ showBalance() contractState.publicKey, account2.publicKey, ); - return connection.sendTransaction(transaction, approver1); + return solanaWeb3.sendAndConfirmTransaction( + connection, + transaction, + approver1, + ); }) .then(confirmTransaction) .then(showBalance) @@ -146,7 +124,11 @@ showBalance() contractState.publicKey, account2.publicKey, ); - return connection.sendTransaction(transaction, approver2); + return solanaWeb3.sendAndConfirmTransaction( + connection, + transaction, + approver2, + ); }) .then(confirmTransaction) .then(showBalance) diff --git a/web3.js/package-lock.json b/web3.js/package-lock.json index 9618b09da4..44ed82436c 100644 --- a/web3.js/package-lock.json +++ b/web3.js/package-lock.json @@ -5851,7 +5851,7 @@ "dependencies": { "marked": { "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz", "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true }, @@ -6115,7 +6115,7 @@ }, "marked": { "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz", "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true }, @@ -12399,7 +12399,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -16878,7 +16878,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } diff --git a/web3.js/src/budget-program.js b/web3.js/src/budget-program.js index 7e24b5f288..3661c200a2 100644 --- a/web3.js/src/budget-program.js +++ b/web3.js/src/budget-program.js @@ -4,7 +4,7 @@ import * as BufferLayout from 'buffer-layout'; import {Transaction} from './transaction'; import {PublicKey} from './publickey'; -import * as Layout from './layout'; +import {SystemProgram} from './system-program'; /** * Represents a condition that is met by executing a `applySignature()` @@ -114,20 +114,10 @@ function serializeCondition(condition: BudgetCondition) { return data; } case 'signature': { - const dataLayout = BufferLayout.struct([ - BufferLayout.u32('condition'), - Layout.publicKey('from'), - ]); - const from = condition.from.toBuffer(); const data = Buffer.alloc(4 + from.length); - dataLayout.encode( - { - instruction: 1, // Signature - from, - }, - data, - ); + data.writeUInt32LE(1, 0); // Condition enum = Signature + from.copy(data, 4); return data; } default: @@ -190,8 +180,8 @@ export class BudgetProgram { pos += 4; switch (conditions.length) { - case 0: - data.writeUInt32LE(0, pos); // Budget enum = Pay + case 0: { + data.writeUInt32LE(0, pos); // BudgetExpr enum = Pay pos += 4; { @@ -199,17 +189,27 @@ export class BudgetProgram { payment.copy(data, pos); pos += payment.length; } + const trimmedData = data.slice(0, pos); - return new Transaction().add({ + const transaction = SystemProgram.createAccount( + from, + program, + amount, + trimmedData.length, + this.programId, + ); + + return transaction.add({ keys: [ - {pubkey: from, isSigner: true, isDebitable: true}, {pubkey: to, isSigner: false, isDebitable: false}, + {pubkey: program, isSigner: false, isDebitable: true}, ], programId: this.programId, - data: data.slice(0, pos), + data: trimmedData, }); - case 1: - data.writeUInt32LE(1, pos); // Budget enum = After + } + case 1: { + data.writeUInt32LE(1, pos); // BudgetExpr enum = After pos += 4; { const condition = conditions[0]; @@ -218,23 +218,32 @@ export class BudgetProgram { conditionData.copy(data, pos); pos += conditionData.length; + data.writeUInt32LE(0, pos); // BudgetExpr enum = Pay + pos += 4; + const paymentData = serializePayment({amount, to}); paymentData.copy(data, pos); pos += paymentData.length; } + const trimmedData = data.slice(0, pos); - return new Transaction().add({ - keys: [ - {pubkey: from, isSigner: true, isDebitable: true}, - {pubkey: program, isSigner: false, isDebitable: true}, - {pubkey: to, isSigner: false, isDebitable: false}, - ], + const transaction = SystemProgram.createAccount( + from, + program, + amount, + trimmedData.length, + this.programId, + ); + + return transaction.add({ + keys: [{pubkey: program, isSigner: false, isDebitable: true}], programId: this.programId, - data: data.slice(0, pos), + data: trimmedData, }); + } - case 2: - data.writeUInt32LE(2, pos); // Budget enum = Or + case 2: { + data.writeUInt32LE(2, pos); // BudgetExpr enum = Or pos += 4; for (let condition of conditions) { @@ -242,20 +251,29 @@ export class BudgetProgram { conditionData.copy(data, pos); pos += conditionData.length; + data.writeUInt32LE(0, pos); // BudgetExpr enum = Pay + pos += 4; + const paymentData = serializePayment({amount, to}); paymentData.copy(data, pos); pos += paymentData.length; } + const trimmedData = data.slice(0, pos); - return new Transaction().add({ - keys: [ - {pubkey: from, isSigner: true, isDebitable: true}, - {pubkey: program, isSigner: false, isDebitable: true}, - {pubkey: to, isSigner: false, isDebitable: false}, - ], + const transaction = SystemProgram.createAccount( + from, + program, + amount, + trimmedData.length, + this.programId, + ); + + return transaction.add({ + keys: [{pubkey: program, isSigner: false, isDebitable: true}], programId: this.programId, - data: data.slice(0, pos), + data: trimmedData, }); + } default: throw new Error( @@ -282,7 +300,7 @@ export class BudgetProgram { data.writeUInt32LE(0, pos); // NewBudget instruction pos += 4; - data.writeUInt32LE(3, pos); // Budget enum = And + data.writeUInt32LE(3, pos); // BudgetExpr enum = And pos += 4; for (let condition of [condition1, condition2]) { @@ -291,18 +309,27 @@ export class BudgetProgram { pos += conditionData.length; } + data.writeUInt32LE(0, pos); // BudgetExpr enum = Pay + pos += 4; + const paymentData = serializePayment({amount, to}); paymentData.copy(data, pos); pos += paymentData.length; - return new Transaction().add({ - keys: [ - {pubkey: from, isSigner: true, isDebitable: true}, - {pubkey: program, isSigner: false, isDebitable: true}, - {pubkey: to, isSigner: false, isDebitable: false}, - ], + const trimmedData = data.slice(0, pos); + + const transaction = SystemProgram.createAccount( + from, + program, + amount, + trimmedData.length, + this.programId, + ); + + return transaction.add({ + keys: [{pubkey: program, isSigner: false, isDebitable: true}], programId: this.programId, - data: data.slice(0, pos), + data: trimmedData, }); } diff --git a/web3.js/src/transaction.js b/web3.js/src/transaction.js index 444dafaf6e..a616621483 100644 --- a/web3.js/src/transaction.js +++ b/web3.js/src/transaction.js @@ -164,6 +164,8 @@ export class Transaction { let numCreditOnlySignedAccounts = 0; let numCreditOnlyUnsignedAccounts = 0; + const programIds = []; + this.instructions.forEach(instruction => { instruction.keys.forEach(keySignerPair => { const keyStr = keySignerPair.pubkey.toString(); @@ -183,6 +185,12 @@ export class Transaction { }); const programId = instruction.programId.toString(); + if (!programIds.includes(programId)) { + programIds.push(programId); + } + }); + + programIds.forEach(programId => { if (!keys.includes(programId)) { keys.push(programId); numCreditOnlyUnsignedAccounts += 1; diff --git a/web3.js/test/budget-program.test.js b/web3.js/test/budget-program.test.js index 03d6bb9f56..b9431db86f 100644 --- a/web3.js/test/budget-program.test.js +++ b/web3.js/test/budget-program.test.js @@ -15,7 +15,8 @@ test('pay', () => { to.publicKey, 123, ); - expect(transaction.keys).toHaveLength(2); + expect(transaction.instructions[0].keys).toHaveLength(2); + expect(transaction.instructions[1].keys).toHaveLength(2); // TODO: Validate transaction contents more transaction = BudgetProgram.pay( @@ -25,7 +26,8 @@ test('pay', () => { 123, BudgetProgram.signatureCondition(from.publicKey), ); - expect(transaction.keys).toHaveLength(3); + expect(transaction.instructions[0].keys).toHaveLength(2); + expect(transaction.instructions[1].keys).toHaveLength(1); // TODO: Validate transaction contents more transaction = BudgetProgram.pay( @@ -36,7 +38,8 @@ test('pay', () => { BudgetProgram.signatureCondition(from.publicKey), BudgetProgram.timestampCondition(from.publicKey, new Date()), ); - expect(transaction.keys).toHaveLength(3); + expect(transaction.instructions[0].keys).toHaveLength(2); + expect(transaction.instructions[1].keys).toHaveLength(1); // TODO: Validate transaction contents more transaction = BudgetProgram.payOnBoth( @@ -47,7 +50,8 @@ test('pay', () => { BudgetProgram.signatureCondition(from.publicKey), BudgetProgram.timestampCondition(from.publicKey, new Date()), ); - expect(transaction.keys).toHaveLength(3); + expect(transaction.instructions[0].keys).toHaveLength(2); + expect(transaction.instructions[1].keys).toHaveLength(1); // TODO: Validate transaction contents more });