Add BudgetProgram.payOnBoth
This commit is contained in:
parent
63382b7e6b
commit
e3703cec29
|
@ -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);
|
||||||
|
});
|
|
@ -163,8 +163,7 @@ export class BudgetProgram {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a transaction that transfer tokens once a set of conditions are
|
* Generates a transaction that transfers tokens once any of the conditions are met
|
||||||
* met
|
|
||||||
*/
|
*/
|
||||||
static pay(
|
static pay(
|
||||||
from: PublicKey,
|
from: PublicKey,
|
||||||
|
@ -173,7 +172,6 @@ export class BudgetProgram {
|
||||||
amount: number,
|
amount: number,
|
||||||
...conditions: Array<BudgetCondition>
|
...conditions: Array<BudgetCondition>
|
||||||
): Transaction {
|
): Transaction {
|
||||||
|
|
||||||
const userdata = Buffer.alloc(1024);
|
const userdata = Buffer.alloc(1024);
|
||||||
let pos = 0;
|
let pos = 0;
|
||||||
userdata.writeUInt32LE(0, pos); // NewContract instruction
|
userdata.writeUInt32LE(0, pos); // NewContract instruction
|
||||||
|
@ -222,7 +220,7 @@ export class BudgetProgram {
|
||||||
});
|
});
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
userdata.writeUInt32LE(2, pos); // Budget enum = Ok
|
userdata.writeUInt32LE(2, pos); // Budget enum = Or
|
||||||
pos += 4;
|
pos += 4;
|
||||||
|
|
||||||
for (let condition of conditions) {
|
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 {
|
static applyTimestamp(from: PublicKey, program: PublicKey, to: PublicKey, when: Date): Transaction {
|
||||||
const whenData = serializeDate(when);
|
const whenData = serializeDate(when);
|
||||||
const userdata = Buffer.alloc(4 + whenData.length);
|
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 {
|
static applySignature(from: PublicKey, program: PublicKey, to: PublicKey): Transaction {
|
||||||
const userdata = Buffer.alloc(4);
|
const userdata = Buffer.alloc(4);
|
||||||
userdata.writeUInt32LE(2, 0); // ApplySignature instruction
|
userdata.writeUInt32LE(2, 0); // ApplySignature instruction
|
||||||
|
|
|
@ -38,6 +38,17 @@ test('pay', () => {
|
||||||
);
|
);
|
||||||
expect(transaction.keys).toHaveLength(3);
|
expect(transaction.keys).toHaveLength(3);
|
||||||
// TODO: Validate transaction contents more
|
// 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', () => {
|
test('apply', () => {
|
||||||
|
|
Loading…
Reference in New Issue