Add BudgetProgram.payOnBoth

This commit is contained in:
Michael Vines 2018-09-26 09:49:59 -07:00
parent 63382b7e6b
commit e3703cec29
4 changed files with 208 additions and 4 deletions

View File

@ -0,0 +1,146 @@
/*
Example of using the Budget program to perform a payment authorized by two parties
*/
//eslint-disable-next-line import/no-commonjs
const solanaWeb3 = require('..');
//const solanaWeb3 = require('@solana/web3.js');
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();
const approver2 = new solanaWeb3.Account();
let url;
url = 'http://localhost:8899';
//url = 'http://testnet.solana.com:8899';
const connection = new solanaWeb3.Connection(url);
function showBalance() {
console.log(`\n== Account State`);
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}`);
});
}
function confirmTransaction(signature) {
console.log('Confirming transaction:', signature);
return connection.confirmTransaction(signature)
.then((confirmation) => {
if (!confirmation) {
throw new Error('Transaction was not confirmed');
}
console.log('Transaction confirmed');
});
}
function airDrop() {
console.log(`\n== Requesting airdrop of 100 to ${account1.publicKey}`);
return connection.requestAirdrop(account1.publicKey, 100)
.then(confirmTransaction);
}
showBalance()
.then(airDrop)
.then(() => {
console.log(`\n== Move 1 token to approver1`);
const transaction = solanaWeb3.SystemProgram.move(
account1.publicKey,
approver1.publicKey,
1,
);
return connection.sendTransaction(account1, transaction);
})
.then(confirmTransaction)
.then(() => {
console.log(`\n== Move 1 token to approver2`);
const transaction = solanaWeb3.SystemProgram.move(
account1.publicKey,
approver2.publicKey,
1,
);
return connection.sendTransaction(account1, transaction);
})
.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 tokens to transfer
0,
solanaWeb3.BudgetProgram.programId,
);
return connection.sendTransaction(account1, transaction);
})
.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 token to hold the contract state
solanaWeb3.BudgetProgram.space,
solanaWeb3.BudgetProgram.programId,
);
return connection.sendTransaction(account1, transaction);
})
.then(confirmTransaction)
.then(showBalance)
.then(() => {
console.log(`\n== Initializing contract`);
const transaction = solanaWeb3.BudgetProgram.payOnBoth(
contractFunds.publicKey,
contractState.publicKey,
account2.publicKey,
50,
solanaWeb3.BudgetProgram.signatureCondition(approver1.publicKey),
solanaWeb3.BudgetProgram.signatureCondition(approver2.publicKey),
);
return connection.sendTransaction(contractFunds, transaction);
})
.then(confirmTransaction)
.then(showBalance)
.then(() => {
console.log(`\n== Apply approver 1`);
const transaction = solanaWeb3.BudgetProgram.applySignature(
approver1.publicKey,
contractState.publicKey,
account2.publicKey,
);
return connection.sendTransaction(approver1, transaction);
})
.then(confirmTransaction)
.then(showBalance)
.then(() => {
console.log(`\n== Apply approver 2`);
const transaction = solanaWeb3.BudgetProgram.applySignature(
approver2.publicKey,
contractState.publicKey,
account2.publicKey,
);
return connection.sendTransaction(approver2, transaction);
})
.then(confirmTransaction)
.then(showBalance)
.then(() => {
console.log('\nDone');
})
.catch((err) => {
console.log(err);
});

View File

@ -163,8 +163,7 @@ export class BudgetProgram {
}
/**
* Generates a transaction that transfer tokens once a set of conditions are
* met
* Generates a transaction that transfers tokens once any of the conditions are met
*/
static pay(
from: PublicKey,
@ -173,7 +172,6 @@ export class BudgetProgram {
amount: number,
...conditions: Array<BudgetCondition>
): Transaction {
const userdata = Buffer.alloc(1024);
let pos = 0;
userdata.writeUInt32LE(0, pos); // NewContract instruction
@ -222,7 +220,7 @@ export class BudgetProgram {
});
case 2:
userdata.writeUInt32LE(2, pos); // Budget enum = Ok
userdata.writeUInt32LE(2, pos); // Budget enum = Or
pos += 4;
for (let condition of conditions) {
@ -247,6 +245,51 @@ export class BudgetProgram {
}
}
/**
* Generates a transaction that transfers tokens once both conditions are met
*/
static payOnBoth(
from: PublicKey,
program: PublicKey,
to: PublicKey,
amount: number,
condition1: BudgetCondition,
condition2: 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(3, pos); // Budget enum = And
pos += 4;
for (let condition of [condition1, condition2]) {
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, program, to],
programId: this.programId,
userdata: userdata.slice(0, pos),
});
}
/**
* Generates a transaction that applies a timestamp, which could enable a
* pending payment to proceed.
*/
static applyTimestamp(from: PublicKey, program: PublicKey, to: PublicKey, when: Date): Transaction {
const whenData = serializeDate(when);
const userdata = Buffer.alloc(4 + whenData.length);
@ -262,6 +305,10 @@ export class BudgetProgram {
});
}
/**
* Generates a transaction that applies a signature, which could enable a
* pending payment to proceed.
*/
static applySignature(from: PublicKey, program: PublicKey, to: PublicKey): Transaction {
const userdata = Buffer.alloc(4);
userdata.writeUInt32LE(2, 0); // ApplySignature instruction

View File

@ -38,6 +38,17 @@ test('pay', () => {
);
expect(transaction.keys).toHaveLength(3);
// TODO: Validate transaction contents more
transaction = BudgetProgram.payOnBoth(
from.publicKey,
program.publicKey,
to.publicKey,
123,
BudgetProgram.signatureCondition(from.publicKey),
BudgetProgram.timestampCondition(from.publicKey, new Date()),
);
expect(transaction.keys).toHaveLength(3);
// TODO: Validate transaction contents more
});
test('apply', () => {