mirror of
https://github.com/alexgo-io/stx-atomic-swap.git
synced 2026-01-12 16:53:25 +08:00
442 lines
12 KiB
JavaScript
442 lines
12 KiB
JavaScript
const assert = require('chai').assert;
|
|
const BN = require('bn.js');
|
|
|
|
const {
|
|
start_stx_chain,
|
|
start_btc_chain,
|
|
generate_secret,
|
|
calculate_hash,
|
|
stx_register_swap_intent,
|
|
stx_execute_swap,
|
|
sip009_mint,
|
|
sip009_owner,
|
|
sip010_mint,
|
|
sip010_balance,
|
|
sip009_sip010_htlc_set_whitelisted,
|
|
btc_register_swap_intent,
|
|
btc_execute_swap,
|
|
btc_refund_swap_intent
|
|
} = require('./util');
|
|
|
|
describe('STX <> BTC',async function()
|
|
{
|
|
before(async function()
|
|
{
|
|
console.debug("Starting STX and BTC chains...");
|
|
console.debug("");
|
|
let stx_chain_process, btc_chain_process;
|
|
const kill = () =>
|
|
{
|
|
console.log('Stopping chains...');
|
|
try {stx_chain_process.kill('SIGINT');} catch(e){};
|
|
try {btc_chain_process.kill('SIGINT');} catch(e){};
|
|
};
|
|
process.on('SIGINT',kill);
|
|
process.on('uncaughtException',kill);
|
|
try
|
|
{
|
|
[stx_chain_process,btc_chain_process] = await Promise.all([start_stx_chain(),start_btc_chain()]);
|
|
}
|
|
catch (error)
|
|
{
|
|
console.error(error);
|
|
kill();
|
|
process.exit(1);
|
|
}
|
|
this.stx = stx_chain_process;
|
|
this.btc = btc_chain_process;
|
|
});
|
|
|
|
after(function()
|
|
{
|
|
console.debug("Stopping chains...");
|
|
this.stx.kill();
|
|
this.btc.kill();
|
|
});
|
|
|
|
it("Can swap STX and BTC",async function()
|
|
{
|
|
const secret = await generate_secret();
|
|
const hash = calculate_hash(secret);
|
|
const stx_expiration = await this.stx.block_height(10);
|
|
const stx_amount = 340;
|
|
const btc_expiration = (await this.btc.block_height(20)).toNumber();
|
|
const btc_amount = 1.3; // 1.3 BTC
|
|
const {deployer, wallet_1} = this.stx.session.accounts;
|
|
const [, btc_wallet_1, btc_wallet_2] = this.btc.session.accounts;
|
|
|
|
const party_a = {
|
|
stx_address: deployer.address,
|
|
btc_account: btc_wallet_1
|
|
};
|
|
|
|
const party_b = {
|
|
stx_address: wallet_1.address,
|
|
btc_account: btc_wallet_2
|
|
};
|
|
|
|
// STX side
|
|
const stx_side = {
|
|
stx_chain: this.stx,
|
|
sender: party_a.stx_address,
|
|
recipient: party_b.stx_address,
|
|
hash,
|
|
amount_or_token_id: stx_amount,
|
|
expiration_height: stx_expiration
|
|
};
|
|
|
|
await stx_register_swap_intent(stx_side);
|
|
|
|
|
|
// BTC side
|
|
const btc_side = await btc_register_swap_intent({
|
|
btc_chain: this.btc,
|
|
sender: party_b.btc_account,
|
|
recipient_public_key: party_a.btc_account.publicKey,
|
|
hash,
|
|
amount: btc_amount,
|
|
expiration_height: btc_expiration,
|
|
network: 'regtest',
|
|
tx_fee_sat: 500
|
|
});
|
|
|
|
const party_a_starting_btc_balance = await this.btc.balance(party_a.btc_account.address);
|
|
const party_b_starting_stx_balance = await this.stx.balance(party_b.stx_address);
|
|
|
|
// Execute swap
|
|
const stx_swap = await stx_execute_swap({
|
|
...stx_side,
|
|
preimage: secret
|
|
});
|
|
|
|
const btc_swap = await btc_execute_swap({
|
|
...btc_side,
|
|
btc_chain: this.btc,
|
|
preimage: secret,
|
|
recipient: party_a.btc_account
|
|
});
|
|
|
|
assert.isOk(stx_swap);
|
|
assert.isOk(btc_swap);
|
|
assert.equal(((await this.btc.balance(party_a.btc_account.address)) - party_a_starting_btc_balance).toFixed(8), btc_amount.toFixed(8), "Unexpected BTC balance");
|
|
assert.isTrue((await this.stx.balance(party_b.stx_address)).gt(party_b_starting_stx_balance), "STX balance did not increase");
|
|
});
|
|
|
|
it("Can swap SIP009 and BTC",async function()
|
|
{
|
|
const secret = await generate_secret();
|
|
const hash = calculate_hash(secret);
|
|
|
|
const {deployer, wallet_1} = this.stx.session.accounts;
|
|
const [, btc_wallet_1, btc_wallet_2] = this.btc.session.accounts;
|
|
|
|
const party_a = {
|
|
stx_address: deployer.address,
|
|
btc_account: btc_wallet_1
|
|
};
|
|
|
|
const party_b = {
|
|
stx_address: wallet_1.address,
|
|
btc_account: btc_wallet_2
|
|
};
|
|
|
|
const sip009 = await sip009_mint(this.stx, party_a.stx_address);
|
|
const stx_expiration = await this.stx.block_height(10);
|
|
const btc_expiration = (await this.btc.block_height(20)).toNumber();
|
|
const btc_amount = 1.6;
|
|
|
|
await sip009_sip010_htlc_set_whitelisted(this.stx, [{token_contract: sip009.asset_contract, whitelisted: true}]);
|
|
|
|
// STX side
|
|
const stx_side = {
|
|
stx_chain: this.stx,
|
|
sender: party_a.stx_address,
|
|
recipient: party_b.stx_address,
|
|
hash,
|
|
amount_or_token_id: sip009.value,
|
|
expiration_height: stx_expiration,
|
|
asset_contract: sip009.asset_contract,
|
|
asset_type: 'sip009'
|
|
};
|
|
|
|
await stx_register_swap_intent(stx_side);
|
|
|
|
// BTC side
|
|
const btc_side = await btc_register_swap_intent({
|
|
btc_chain: this.btc,
|
|
sender: party_b.btc_account,
|
|
recipient_public_key: party_a.btc_account.publicKey,
|
|
hash,
|
|
amount: btc_amount,
|
|
expiration_height: btc_expiration,
|
|
network: 'regtest',
|
|
tx_fee_sat: 500
|
|
});
|
|
|
|
const party_a_starting_btc_balance = await this.btc.balance(party_a.btc_account.address);
|
|
|
|
// Execute swap
|
|
await stx_execute_swap({
|
|
...stx_side,
|
|
preimage: secret
|
|
});
|
|
|
|
await btc_execute_swap({
|
|
...btc_side,
|
|
btc_chain: this.btc,
|
|
preimage: secret,
|
|
recipient: party_a.btc_account
|
|
});
|
|
|
|
assert.equal(((await this.btc.balance(party_a.btc_account.address)) - party_a_starting_btc_balance).toFixed(8), btc_amount.toFixed(8), "Unexpected BTC balance");
|
|
assert.equal(party_b.stx_address, await sip009_owner(this.stx, sip009.value), "Wrong SIP009 owner");
|
|
});
|
|
|
|
it("Can swap SIP010 and BTC",async function()
|
|
{
|
|
const secret = await generate_secret();
|
|
const hash = calculate_hash(secret);
|
|
|
|
const {deployer, wallet_1} = this.stx.session.accounts;
|
|
const [, btc_wallet_1, btc_wallet_2] = this.btc.session.accounts;
|
|
|
|
const party_a = {
|
|
stx_address: deployer.address,
|
|
btc_account: btc_wallet_1
|
|
};
|
|
|
|
const party_b = {
|
|
stx_address: wallet_1.address,
|
|
btc_account: btc_wallet_2
|
|
};
|
|
|
|
const sip010_amount = new BN(5660);
|
|
const sip010 = await sip010_mint(this.stx, party_a.stx_address, sip010_amount);
|
|
const stx_expiration = await this.stx.block_height(10);
|
|
const btc_expiration = (await this.btc.block_height(20)).toNumber();
|
|
const btc_amount = 1.7;
|
|
|
|
await sip009_sip010_htlc_set_whitelisted(this.stx, [{token_contract: sip010.asset_contract, whitelisted: true}]);
|
|
|
|
// STX side
|
|
const stx_side = {
|
|
stx_chain: this.stx,
|
|
sender: party_a.stx_address,
|
|
recipient: party_b.stx_address,
|
|
hash,
|
|
amount_or_token_id: sip010_amount,
|
|
expiration_height: stx_expiration,
|
|
asset_contract: sip010.asset_contract,
|
|
asset_type: 'sip010'
|
|
};
|
|
|
|
await stx_register_swap_intent(stx_side);
|
|
|
|
// BTC side
|
|
const btc_side = await btc_register_swap_intent({
|
|
btc_chain: this.btc,
|
|
sender: party_b.btc_account,
|
|
recipient_public_key: party_a.btc_account.publicKey,
|
|
hash,
|
|
amount: btc_amount,
|
|
expiration_height: btc_expiration,
|
|
network: 'regtest',
|
|
tx_fee_sat: 500
|
|
});
|
|
|
|
const party_a_starting_btc_balance = await this.btc.balance(party_a.btc_account.address);
|
|
const party_b_sip010_starting_balance = await sip010_balance(this.stx, stx_side.recipient);
|
|
|
|
// Execute swap
|
|
await stx_execute_swap({
|
|
...stx_side,
|
|
preimage: secret
|
|
});
|
|
|
|
await btc_execute_swap({
|
|
...btc_side,
|
|
btc_chain: this.btc,
|
|
preimage: secret,
|
|
recipient: party_a.btc_account
|
|
});
|
|
|
|
assert.equal(((await this.btc.balance(party_a.btc_account.address)) - party_a_starting_btc_balance).toFixed(8), btc_amount.toFixed(8), "Unexpected BTC balance");
|
|
assert.isTrue((await sip010_balance(this.stx, stx_side.recipient)).sub(party_b_sip010_starting_balance).eq(sip010_amount), "SIP010 balance did not increase by the right amount");
|
|
});
|
|
|
|
it("BTC HTLC rejects wrong preimage",async function()
|
|
{
|
|
const secret = await generate_secret(); // shorter for BTC
|
|
const hash = calculate_hash(secret);
|
|
const btc_expiration = (await this.btc.block_height(20)).toNumber();
|
|
const btc_amount = 1.8;
|
|
|
|
const party_a = {btc_account: this.btc.session.accounts[1]};
|
|
const party_b = {btc_account: this.btc.session.accounts[2]};
|
|
|
|
const btc_side = await btc_register_swap_intent({
|
|
btc_chain: this.btc,
|
|
sender: party_b.btc_account,
|
|
recipient_public_key: party_a.btc_account.publicKey,
|
|
hash,
|
|
amount: btc_amount,
|
|
expiration_height: btc_expiration,
|
|
network: 'regtest',
|
|
tx_fee_sat: 500
|
|
});
|
|
|
|
const btc_swap = btc_execute_swap({
|
|
...btc_side,
|
|
btc_chain: this.btc,
|
|
preimage: Buffer.from('bogus'),
|
|
recipient: party_a.btc_account
|
|
});
|
|
|
|
return btc_swap
|
|
.then(
|
|
() => assert.fail('Should have failed'),
|
|
error => assert.include(error.message, 'non-mandatory-script-verify-flag')
|
|
);
|
|
});
|
|
|
|
it("Sender can recover BTC from HTLC after expiry",async function()
|
|
{
|
|
const secret = await generate_secret(); // shorter for BTC
|
|
const hash = calculate_hash(secret);
|
|
const btc_blocks = 20;
|
|
const btc_expiration = (await this.btc.block_height(btc_blocks)).toNumber();
|
|
const btc_amount = 1.9;
|
|
|
|
const party_a = {btc_account: this.btc.session.accounts[1]};
|
|
const party_b = {btc_account: this.btc.session.accounts[2]};
|
|
|
|
const btc_side = await btc_register_swap_intent({
|
|
btc_chain: this.btc,
|
|
sender: party_b.btc_account,
|
|
recipient_public_key: party_a.btc_account.publicKey,
|
|
hash,
|
|
amount: btc_amount,
|
|
expiration_height: btc_expiration,
|
|
network: 'regtest',
|
|
tx_fee_sat: 500
|
|
});
|
|
|
|
const party_b_starting_btc_balance = await this.btc.balance(party_b.btc_account.address);
|
|
|
|
await this.btc.mine_empty_blocks(btc_blocks); // advance chain
|
|
|
|
const btc_swap = await btc_refund_swap_intent({
|
|
...btc_side,
|
|
btc_chain: this.btc,
|
|
preimage: Buffer.from([]),
|
|
recipient: party_b.btc_account
|
|
});
|
|
|
|
assert.isOk(btc_swap);
|
|
assert.equal(((await this.btc.balance(party_b.btc_account.address)) - party_b_starting_btc_balance).toFixed(8), btc_amount.toFixed(8), "Unexpected BTC balance");
|
|
});
|
|
|
|
it("Sender cannot recover BTC from HTLC before expiry",async function()
|
|
{
|
|
const secret = await generate_secret(); // shorter for BTC
|
|
const hash = calculate_hash(secret);
|
|
const btc_blocks = 20;
|
|
const btc_expiration = (await this.btc.block_height(btc_blocks)).toNumber();
|
|
const btc_amount = 2.1;
|
|
|
|
const party_a = {btc_account: this.btc.session.accounts[1]};
|
|
const party_b = {btc_account: this.btc.session.accounts[2]};
|
|
|
|
const btc_side = await btc_register_swap_intent({
|
|
btc_chain: this.btc,
|
|
sender: party_b.btc_account,
|
|
recipient_public_key: party_a.btc_account.publicKey,
|
|
hash,
|
|
amount: btc_amount,
|
|
expiration_height: btc_expiration,
|
|
network: 'regtest',
|
|
tx_fee_sat: 500
|
|
});
|
|
|
|
return btc_refund_swap_intent({
|
|
...btc_side,
|
|
btc_chain: this.btc,
|
|
preimage: Buffer.from([]),
|
|
recipient: party_b.btc_account
|
|
})
|
|
.then(
|
|
() => assert.fail('Should have failed'),
|
|
error => assert.include(error.message,'non-final')
|
|
);
|
|
});
|
|
|
|
it("Receiver cannot recover BTC from HTLC after expiry",async function()
|
|
{
|
|
const secret = await generate_secret(); // shorter for BTC
|
|
const hash = calculate_hash(secret);
|
|
const btc_blocks = 20;
|
|
const btc_expiration = (await this.btc.block_height(btc_blocks)).toNumber();
|
|
const btc_amount = 2.2;
|
|
|
|
const party_a = {btc_account: this.btc.session.accounts[1]};
|
|
const party_b = {btc_account: this.btc.session.accounts[2]};
|
|
|
|
const btc_side = await btc_register_swap_intent({
|
|
btc_chain: this.btc,
|
|
sender: party_b.btc_account,
|
|
recipient_public_key: party_a.btc_account.publicKey,
|
|
hash,
|
|
amount: btc_amount,
|
|
expiration_height: btc_expiration,
|
|
network: 'regtest',
|
|
tx_fee_sat: 500
|
|
});
|
|
|
|
await this.btc.mine_empty_blocks(btc_blocks); // advance chain
|
|
|
|
return btc_refund_swap_intent({
|
|
...btc_side,
|
|
btc_chain: this.btc,
|
|
preimage: Buffer.from([]),
|
|
recipient: party_a.btc_account
|
|
})
|
|
.then(
|
|
() => assert.fail('Should have failed'),
|
|
error => assert.include(error.message,'non-mandatory-script-verify-flag')
|
|
);
|
|
});
|
|
|
|
it("Sender cannot recover BTC from HTLC with preimage",async function()
|
|
{
|
|
const secret = await generate_secret(); // shorter for BTC
|
|
const hash = calculate_hash(secret);
|
|
const btc_blocks = 20;
|
|
const btc_expiration = (await this.btc.block_height(btc_blocks)).toNumber();
|
|
const btc_amount = 2.3;
|
|
|
|
const party_a = {btc_account: this.btc.session.accounts[1]};
|
|
const party_b = {btc_account: this.btc.session.accounts[2]};
|
|
|
|
const btc_side = await btc_register_swap_intent({
|
|
btc_chain: this.btc,
|
|
sender: party_b.btc_account,
|
|
recipient_public_key: party_a.btc_account.publicKey,
|
|
hash,
|
|
amount: btc_amount,
|
|
expiration_height: btc_expiration,
|
|
network: 'regtest',
|
|
tx_fee_sat: 500
|
|
});
|
|
|
|
return btc_execute_swap({
|
|
...btc_side,
|
|
btc_chain: this.btc,
|
|
preimage: secret,
|
|
recipient: party_b.btc_account
|
|
})
|
|
.then(
|
|
() => assert.fail('Should have failed'),
|
|
error => assert.include(error.message, 'non-mandatory-script-verify-flag')
|
|
);
|
|
});
|
|
}); |