I wanted to use this OmniLayer protocol, but have found no examples in the whole internet. I think it would be good to include such examples in the tests.
Here's a small snippet: https://gist.github.com/caffeinum/f64a51ce55d5ac9075bb2f5f2f439c0d
Undecided whether our examples are the right place for that...
That makes sense
maybe at least some reference in the OP_RETURN section. I had a hard time breaking this and wanted to share
@caffeinum have you tried the new payments API? Specifically bitcoin.payments.embed?
@caffeinum Modified your example to work with bitcoinjs-lib v4 (Marked changes with NEW**)
const bitcoin = require('bitcoinjs-lib')
const request = require('request-promise-native')
const net = process.env.NETWORK === 'testnet'
? bitcoin.networks.testnet
: bitcoin.networks.bitcoin
const API = net === bitcoin.networks.testnet
? `https://test-insight.swap.online/insight-api`
: `https://insight.bitpay.com/api`
const fetchUnspents = (address) =>
request(`${API}/addr/${address}/utxo/`).then(JSON.parse)
const broadcastTx = (txRaw) =>
request.post(`${API}/tx/send`, {
json: true,
body: {
rawtx: txRaw,
},
})
const createSimpleSend = async (fetchUnspents, alice_pair, recipient_address/*, amount = 10*/) => {
const tx = new bitcoin.TransactionBuilder(net)
const alice_p2pkh = bitcoin.payments.p2pkh({
pubkey: alice_pair.publicKey,
network: net
}).address // NEW** New Payments API for p2pkh
const unspents = await fetchUnspents(alice_p2pkh)
const fundValue = 546 // dust
const feeValue = 5000
const totalUnspent = unspents.reduce((summ, { satoshis }) => summ + satoshis, 0)
const skipValue = totalUnspent - fundValue - feeValue
if (totalUnspent < feeValue + fundValue) {
throw new Error(`Total less than fee: ${totalUnspent} < ${feeValue} + ${fundValue}`)
}
unspents.forEach(({ txid, vout }) => tx.addInput(txid, vout, 0xfffffffe))
const simple_send = [
"6f6d6e69", // omni
"0000", // version
"00000000001f", // 31 for Tether
"000000003B9ACA00" // amount = 10 * 100 000 000 in HEX
].join('')
const data = [ Buffer.from(simple_send, "hex") ] // NEW** data must be an Array(Buffer)
const omniOutput = bitcoin.payments.embed({ data }).output // NEW** Payments API
tx.addOutput(recipient_address, fundValue) // should be first!
tx.addOutput(omniOutput, 0)
tx.addOutput(alice_p2pkh, skipValue)
// NEW** tx.inputs was deprecated, since you use unspents 1-for-1 to make inputs I used unspents.
unspents.forEach((unspent, index) => {
tx.sign(index, alice_pair)
})
return tx
}
// Construct tx
const alice = bitcoin.ECPair.fromWIF(process.env.ALICE_WIF, net)
const bobby = bitcoin.ECPair.makeRandom({ network: net })
const amount = null // not used
// NEW** Used new payments API for bobby
const omni_tx = createSimpleSend(fetchUnspents, alice, bitcoin.payments.p2pkh({ pubkey: bobby.publicKey, network: net }).address, amount)
const auto_send = false
omni_tx.then(tx => {
const txRaw = tx.buildIncomplete()
console.log('hash', txRaw.getId())
console.log(`"${txRaw.toHex()}"`)
console.log(txRaw)
if (auto_send) {
broadcastTx(txRaw.toHex())
}
})
@dcousens
if data is a Buffer, I think we should assume it should be a single push and just make it into a single item Array automatically.
I don't know if that is great idea...
TransactionBuilder.inputs.forEach
I don't know why.
If you had an inputs array to begin with (unspents!), you should use that no?
If you had an inputs array to begin with (unspents!), you should use that no?
This assumes you use all items of the Array. (array.length === txb.inputCount)
Perhaps a getter for txb.inputCount and outputCount that grabs the current count internally...
Then we could do:
Array(txb.inputCount).fill(0).forEach((item, index) => {
console.log(index)
})
But at that point, a for loop looks cleaner and easier to understand... :-/
@junderw yes, you should always use all the unspents provided. If you don't want to use them all... filter them to what you need.
See coinselect for that workflow...
you should always use all the unspents provided
What about when they don't have the unspents? Like a rawtx.
If we don't want them to touch the internals of Tx and Txb then how should they gather how many inputs they have?
@junderw great question.
I don't know
We don't want to encourage merely iterating over the equivalent of txb.inputs, as, it means they might not have checked what those inputs are...
PSBT to the rescue!
@junderw is it though?
@dcousens hahahahahahahahahaha
Well............... it's better than not having it IMO.
" tx.addOutput(recipient_address, fundValue) // should be first! ",why that it should be first , i have used the code ,but the signd transaction is something wrong at the referenceaddress .
Read the Omni Protocol documents.
It should be first because the Omni protocol requires it.
@Nick-49 which bitcoinjs-lib version you were using? My snippet does not work with 4.0 version yet, but there is an updated version above: https://github.com/bitcoinjs/bitcoinjs-lib/issues/1176#issuecomment-419014085
I have found the "bug" of my code . And i have solved it. thank you~ @caffeinum @junderw
Hi,
What mean "000000003B9ACA00" // amount = 10 * 100 000 000 in HEX ?
It should be change dynamically?
@ssssssu12 yes, its the amount of Omni tokens to send
This is a detailed demo
@yugasun txb.addInput second arg is not amount, it's the UTXO vout index.
@junderw thanks for your correction, I made a mistake.
@caffeinum Modified your example to work with bitcoinjs-lib v4 (Marked changes with
NEW**)const bitcoin = require('bitcoinjs-lib') const request = require('request-promise-native') const net = process.env.NETWORK === 'testnet' ? bitcoin.networks.testnet : bitcoin.networks.bitcoin const API = net === bitcoin.networks.testnet ? `https://test-insight.swap.online/insight-api` : `https://insight.bitpay.com/api` const fetchUnspents = (address) => request(`${API}/addr/${address}/utxo/`).then(JSON.parse) const broadcastTx = (txRaw) => request.post(`${API}/tx/send`, { json: true, body: { rawtx: txRaw, }, }) const createSimpleSend = async (fetchUnspents, alice_pair, recipient_address/*, amount = 10*/) => { const tx = new bitcoin.TransactionBuilder(net) const alice_p2pkh = bitcoin.payments.p2pkh({ pubkey: alice_pair.publicKey, network: net }).address // NEW** New Payments API for p2pkh const unspents = await fetchUnspents(alice_p2pkh) const fundValue = 546 // dust const feeValue = 5000 const totalUnspent = unspents.reduce((summ, { satoshis }) => summ + satoshis, 0) const skipValue = totalUnspent - fundValue - feeValue if (totalUnspent < feeValue + fundValue) { throw new Error(`Total less than fee: ${totalUnspent} < ${feeValue} + ${fundValue}`) } unspents.forEach(({ txid, vout }) => tx.addInput(txid, vout, 0xfffffffe)) const simple_send = [ "6f6d6e69", // omni "0000", // version "00000000001f", // 31 for Tether "000000003B9ACA00" // amount = 10 * 100 000 000 in HEX ].join('') const data = [ Buffer.from(simple_send, "hex") ] // NEW** data must be an Array(Buffer) const omniOutput = bitcoin.payments.embed({ data }).output // NEW** Payments API tx.addOutput(recipient_address, fundValue) // should be first! tx.addOutput(omniOutput, 0) tx.addOutput(alice_p2pkh, skipValue) // NEW** tx.inputs was deprecated, since you use unspents 1-for-1 to make inputs I used unspents. unspents.forEach((unspent, index) => { tx.sign(index, alice_pair) }) return tx } // Construct tx const alice = bitcoin.ECPair.fromWIF(process.env.ALICE_WIF, net) const bobby = bitcoin.ECPair.makeRandom({ network: net }) const amount = null // not used // NEW** Used new payments API for bobby const omni_tx = createSimpleSend(fetchUnspents, alice, bitcoin.payments.p2pkh({ pubkey: bobby.publicKey, network: net }).address, amount) const auto_send = false omni_tx.then(tx => { const txRaw = tx.buildIncomplete() console.log('hash', txRaw.getId()) console.log(`"${txRaw.toHex()}"`) console.log(txRaw) if (auto_send) { broadcastTx(txRaw.toHex()) } })
It is generate a transaction successfully but that Tx Hash not reflect on Omni Blockchain.

@rohitsahu21 well, tx was not published to bitpay either: https://insight.bitpay.com/tx/ba2ae595f03874b7773548102e12cfe3d6cf0be85791f81b76ed1b2d85b10cce
Did you use NETWORK=mainnet?
@caffeinum No i'm using Testnet for Testing purpose
@rohitsahu21 omniexplorer doesn't work for testnet. You need to run your own OmniLayer node if you want to try OMNI tokens on bitcoin testnet. Also, I didn't test this script on testnet, you should check all the values. You can contact me at telegram @caffeinum if you need some extra help.
Hi @caffeinum @junderw ,
I'm looking to develop a USDT wallet (NodeJs) without the need to setup my own Omnilayer node. Would really appreciate for any advice or help regarding the following:
Any help would be greatly appreciate you guys!
Thanks!
https://www.bitgo.com/api/v1/tx/fee
This is also a good API, Bitgo has an ok fee estimator.
feeByBlockTarget["3"] for instance is "satoshis per kB cost if you want your transaction confirmed within the next 3 blocks"
but imo, Bitgo overestimates a tad, so 3 should get in the next block most of the time.
@zinxer
- What are the modifications required if i need to have USDT(propertyid: 31) signed transaction prepared offline so that i could use bitpay or omniexplorer's pushtx API to broadcast the transaction on mainnet?
The example works offline! Only two functions: fetchUnspents, broadcastTx need connection. You can extract or cache needed values, or pass them via other means.
- Why is the fee value fixed at 5000? Did you fix the value at 5000 satoshi since the network fee which is expected to consume would be below that number?
That was arbitrary, you can use any value.
However, there's one pitfall: to estimate fee well, you need to know tx size, but to know tx size, you need to know all the inputs and outputs. However, you could use this simple formula for standard P2SH/P2PKH, only modify it to include OP_RETURN extra size.
const txSize = numInputs * 146 + numOutputsWithoutOmni * 33 + 10 + omniOutputSize
Extra reading:
- Is there an estimate fee API which could be written and is there an example?
I use these nodes for estimation:
https://bitcoinfees.earn.com/api/v1/fees/recommended
https://api.blockcypher.com/v1/btc/main
They give estimate in sat/byte (sat/kb for blockcypher), so to know actual fee, you need to multiply that by tx size. See above ^
P.S. Also, as for offline signing, see Metamask's PR for external signers: https://github.com/MetaMask/metamask-extension/pull/6143, and github.com/flightwallet and https://www.parity.io/signer/ as an examples of offline signers. Probably you can benefit from there examples if you're doing offline wallet
Hi @junderw @caffeinum ,
It seems like it should be this:
const simple_send = [
"6f6d6e69", // omni
"0000", // version
"00000000001f", // 31 for Tether
"00000000", //8 zeros before 2s complement HEX
"<amount HEX in signed 2s complement>" // amount = <amount> * 100 000 000 in HEX
].join('')
instead of:
const simple_send = [
"6f6d6e69", // omni
"0000", // version
"00000000001f", // 31 for Tether
"000000003B9ACA00" // amount = 10 * 100 000 000 in HEX
].join('')
The only problem i'm facing now is to programmatically convert decimals to HEX signed 2s complement string.
No.
You need to encode into 8 bytes if you want to support omni.
Your idea to use only 4 bytes will break for values over 42.94 USDT
6 bytes can support up to 2.8 million USDT ish.
but you should support 8 bytes using a bigint or bignumber library.
Noted @junderw , i'm looking for a function to convert the HEX value with padded leading zeros to fill an 8-byte length amount.
Update:
I've solved this by:
Buffer.byteLength(<amt>, 'hex')@zinxer here's my version of toPaddedHexString:
const toPaddedHexString = (num, len) => {
const str = num.toString(16)
return "0".repeat(len - str.length) + str
}
Usage:
const simple_send = [
"6f6d6e69", // omni
"0000", // tx type
"0000", // version
// "0000001f", // 31 for Tether
toPaddedHexString(coin, 8),
// "000000003B9ACA00" // amount = 10 * 100 000 000 in HEX
toPaddedHexString(Math.floor(amount * 100000000), 16),
].join('')
(from https://github.com/swaponline/swap.core/tree/master/src/swap.swaps/usdt)
Noted thanks guys!
Was wondering how would adding another wallet as part of the input where it's funds are used to pay the network fee while the other's btc is used as dust.
Thanks,
Matt
Updated the example to use the new PSBT from v5.1
const bitcoin = require('bitcoinjs-lib')
const request = require('request-promise-native')
const net = process.env.NETWORK === 'testnet'
? bitcoin.networks.testnet
: bitcoin.networks.bitcoin
const API = net === bitcoin.networks.testnet
? `https://test-insight.swap.online/insight-api`
: `https://insight.bitpay.com/api`
const fetchUnspents = (address) =>
request(`${API}/addr/${address}/utxo/`).then(JSON.parse)
// *NEW need full raw tx for all non-segwit inputs (to verify the value before signing)
const getRawTx = (txid) =>
request(`${API}/rawtx/${txid}/`).then(JSON.parse).then(data => Buffer.from(data.rawtx, 'hex'))
const broadcastTx = (txRaw) =>
request.post(`${API}/tx/send`, {
json: true,
body: {
rawtx: txRaw,
},
})
const createSimpleSend = async (fetchUnspents, alice_pair, recipient_address/*, amount = 10*/) => {
// *NEW PSBT class
const psbt = new bitcoin.Psbt({ network: net })
const alice_p2pkh = bitcoin.payments.p2pkh({
pubkey: alice_pair.publicKey,
network: net
}).address
const unspents = await fetchUnspents(alice_p2pkh)
const fundValue = 546 // dust
const feeValue = 5000
const totalUnspent = unspents.reduce((summ, { satoshis }) => summ + satoshis, 0)
const skipValue = totalUnspent - fundValue - feeValue
if (totalUnspent < feeValue + fundValue) {
throw new Error(`Total less than fee: ${totalUnspent} < ${feeValue} + ${fundValue}`)
}
for (let i = 0; i < unspents.length; i++) {
const nonWitnessUtxo = await getRawTx(unspents[i].txid)
psbt.addInput({
hash: unspents[i].txid,
index: unspents[i].vout,
sequence: 0xfffffffe,
nonWitnessUtxo, // *NEW This will allow us to verify you are signing the correct values for inputs
})
}
const simple_send = [
"6f6d6e69", // omni
"0000", // version
"00000000001f", // 31 for Tether
"000000003B9ACA00" // amount = 10 * 100 000 000 in HEX
].join('')
const data = [ Buffer.from(simple_send, "hex") ]
const omniOutput = bitcoin.payments.embed({ data }).output
psbt.addOutput({ address: recipient_address, value: fundValue }) // should be first!
psbt.addOutput({ script: omniOutput, value: 0 })
psbt.addOutput({ address: alice_p2pkh, value: skipValue })
// *NEW sign all inputs with one method call
psbt.signAllInputs(alice_pair)
return psbt
}
// Construct tx
const alice = bitcoin.ECPair.fromWIF(process.env.ALICE_WIF, net)
const bobby = bitcoin.ECPair.makeRandom({ network: net })
const amount = null // not used
const omni_tx = createSimpleSend(fetchUnspents, alice, bitcoin.payments.p2pkh({ pubkey: bobby.publicKey, network: net }).address, amount)
const auto_send = false
omni_tx.then(psbt => {
// *NEW must finalize before TX extraction. (this helps for multisig)
const txRaw = psbt.finalizeAllInputs().extractTransaction()
console.log('hash', txRaw.getId())
console.log(`"${txRaw.toHex()}"`)
console.log(txRaw)
if (auto_send) {
broadcastTx(txRaw.toHex())
}
})
@junderw
v5.1
It's a test network
According to your latest v5.1 , It is generate a transaction successfully but that Tx Hash not reflect on Omni Blockchain.
hash:0d1cbe2dc0a44f4a28b4e4206490bcc57b7ab9842630fc2cb5b293d11339c853
txRaw:02000000016c198018807bf3cbcac5b3cdb17e01aab2db7e562aacb8dc13d7364b47059532010000006a473044022020cee187538c40479088c03bda62b66d349ada2fea873995490842a312d0909c0220467d520dca411d00ecd05f87c79cf69709b20d4b210c6e6cadeac5c2e3d206ce012103d503dc7b44103147de95b46d6b97b942417090cbaf8d243ad7fee84f067caf1dfeffffff0322020000000000001976a9147d36034630a94241ae48ba14288b73d50608988088ac0000000000000000166a146f6d6e69000000000000001f000000003b9aca00f59d7901000000001976a914ba33eeebd903b3e9ccf874e5e64d989ba37e25e588ac00000000
Does this instance not work on the test network?
Thanks
You didn't broadcast it to the network.
I broadcasted it for you. it should show up soon.
@junderw
Excuse me, The following error occurred when I executed the above script:
(node:19020) UnhandledPromiseRejectionWarning: RequestError: Error: getaddrinfo ENOTFOUND test-insight.swap.online
I tested this script in January and it was ok, but not now, do you have a good solution?
something's wrong with the remote API you're using
@junderw Yes, I also see that this API cannot be accessed. Do you have any other remote API that can be used?Thank you very much!
No... unfortunately a lot of the testnet block explorers are unreliable.
For bitcoinjs-lib we use regtest-server docker container. So when we run integration tests a small docker container with a simple REST server and a regtest bitcoin node are spun up and you can use the regtest-client library to interface with it.
Check the integration tests to see how it is used.
$ docker pull junderw/bitcoinjs-regtest-server
$ docker run -d -p 127.0.0.1:8080:8080 junderw/bitcoinjs-regtest-server
Then once you have this running locally,
import { RegtestUtils } from 'regtest-client';
const APIPASS = 'satoshi';
const APIURL = 'http://127.0.0.1:8080/1';
const regtestUtils = new RegtestUtils({ APIPASS, APIURL });
Then you can do things like
await regtestUtils.mine(10)
To mine 10 blocks etc.
It is much better for testing / messing around.
however, this server does not verify Omni protocol... sooooooo, I don't know what to tell you about that.
Yes, thank you.
hello:
this is my wif L3w9mnJVSqYM9adQomcbwXPRj1rjui5aoVXXnmQ5Yf8qLfy1ZYyL
myAddress = ms7f7uEipu9r4B925XoK8mcJ2zLcNbJiJn
recipient_address = mm1oDPm8vjBfdv92yaMacMGfwpdvfFTeoM
this is my code
const WIF = 'L3w9mnJVSqYM9adQomcbwXPRj1rjui5aoVXXnmQ5Yf8qLfy1ZYyL'
const alice = bitcoin.ECPair.fromWIF(WIF)
const psbt = new bitcoin.Psbt({ network: bitcoin.networks.testnet })
utxo.forEach((item, index, arr) => {
psbt.addInput({
hash: item.txid,
index: item.vout,
nonWitnessUtxo: Buffer.from(item.raw, 'hex'),
})
})
const fee = 1000
const reback = balance - 546 - 1000
const simple_send = [
'6f6d6e69', // omni
'0000', // version
toPaddedHexString(31, 8), // 31 for Tether
,
toPaddedHexString(Math.floor(0.1 * 100000000), 16), // amount = 10 * 100 000 000 in HEX
,
].join('')
const data = [Buffer.from(simple_send, 'hex')]
const omniOutput = bitcoin.payments.embed({ data }).output
psbt.addOutput({ address: 'mm1oDPm8vjBfdv92yaMacMGfwpdvfFTeoM', value: 546 }) // should be first!
psbt.addOutput({ script: omniOutput, value: 0 }) //0.1 omni
psbt.addOutput({ address: 'ms7f7uEipu9r4B925XoK8mcJ2zLcNbJiJn', value: reback })
utxo.forEach((item, index, arr) => {
psbt.signInput(index, alice)
})
psbt.validateSignaturesOfInput(0)
psbt.finalizeAllInputs()
const tx = psbt.extractTransaction().toHex()
console.log(tx)
and I broadcast tx
Here are two IDS I broadcast
2e2888b88291c38248ae5fd1760a37d6c82e9471a52195e5d4d263d6a198a348
c1e55e2d7bb6a57dd80b2e531ee101ba0bfd9b3a137c24cc27a6bb407a403b95
I transferred 0.1 and 0.2 omnin udst to the target address respectively
I don't know how many usdts I have left. I don't know if I have transferred them,
At first, the target address sent me a usdt of Omni, but he said he didn't receive 0.1 and 0.2 from me
I hope someone can help me out. Thank you very much
https://blockexplorer.one/btc/testnet/address/ms7f7uEipu9r4B925XoK8mcJ2zLcNbJiJn/tokens
This site will show you the omni tokens on testnet for your address.
It seems to show a bunch of weird transactions (property ID 1) and it shows you sending tons of USDT even though you never received any. (1000000000 USDT, but you never received 1000000000 USDT)
https://blockexplorer.one/btc/testnet/address/ms7f7uEipu9r4B925XoK8mcJ2zLcNbJiJn/tokens
This site will show you the omni tokens on testnet for your address.
It seems to show a bunch of weird transactions (property ID 1) and it shows you sending tons of USDT even though you never received any. (1000000000 USDT, but you never received 1000000000 USDT)
Thank you very much for your reply. I'm glad to find out the tokens. I haven't got a clue before
thx, I got my omni tokens, I sent three deals in all
once amount = 10 * 100 000 000 in HEX so 1000000000 USD maybe
Later, I knew that someone had transferred a usdt to me, so I changed the quantity to 0.1 and 0.2 and sent them twice, that is to say, neither of them succeeded, but in fact, none of them succeeded. The first time was because I didn't have so many usdts,
const simple_send = [
'6f6d6e69', // omni
'0000', // version
toPaddedHexString(31, 8), // 31 for Tether
,
toPaddedHexString(Math.floor(0.1 * 100000000), 16), // amount = 10 * 100 000 000 in HEX
,
].join('')
toPaddedHexString(Math.floor(0.1 * 100000000), 16), What's wrong with that
You have 0 USDT on the address ms7f7uEipu9r4B925XoK8mcJ2zLcNbJiJn.
hello
I have a transfer of 1000000000 usdt ,you can see
https://blockexplorer.one/omni/testnet/tx/ea25df450df470b27f4c9e98b24bd28d7e6e449b46446880ea7ebfd8413b0ae3
my code like this:
const simple_send = [
"6f6d6e69", // omni
"0000", // version
"00000000001f", // 31 for Tether
"000000003B9ACA00" // amount = 10 * 100 000 000 in HEX
].join('')
so it works
and you say I have 0 USDT on the address ms7f7uEipu9r4B925XoK8mcJ2zLcNbJiJn.
My understanding is USDT Property ID is 31 ,Is that so?
through
https://blockexplorer.one/btc/testnet/address/ms7f7uEipu9r4B925XoK8mcJ2zLcNbJiJn/tokens

I have 1.2 ( Property ID : 1 ),I don't know what it's called
Now I'm trying to transfer out the 1.2 coin. I've tried property 1 and property 31, but none of them works
What I want to ask now is, if I need to transfer this 1.2 coin, How should be filled in, simple_send
"00000000001f", // 31 for Tether
Here you should select 1 instead of 31 (1f is HEX for 31)
Most helpful comment
@caffeinum Modified your example to work with bitcoinjs-lib v4 (Marked changes with
NEW**)