Skip to content

Commit dfb18a7

Browse files
committed
docs(root): create example for interacting with omni
BTC-1864 TICKET: BTC-0
1 parent 48aebc7 commit dfb18a7

File tree

2 files changed

+166
-0
lines changed

2 files changed

+166
-0
lines changed

examples/ts/btc/omni/config.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { BitGoAPI } from '@bitgo/sdk-api';
2+
import { Btc, Tbtc4 } from '@bitgo/sdk-coin-btc';
3+
4+
const env = 'test' as 'test' | 'prod';
5+
6+
const accessToken = '';
7+
const walletId = '';
8+
const walletPassphrase = '';
9+
// optional
10+
const otp = '';
11+
12+
const sdk = new BitGoAPI({ env });
13+
sdk.register('tbtc4', Tbtc4.createInstance);
14+
sdk.register('btc', Btc.createInstance);
15+
sdk.authenticateWithAccessToken({ accessToken });
16+
17+
export const omniConfig = {
18+
env,
19+
coin: env === 'test' ? 'tbtc4' : 'btc',
20+
sdk,
21+
walletPassphrase,
22+
walletId,
23+
otp,
24+
};

examples/ts/btc/omni/index.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* Send or create an omni asset from a multi-sig wallet at BitGo.
3+
*
4+
* Copyright 2024, BitGo, Inc. All Rights Reserved.
5+
*/
6+
import { Wallet } from 'modules/bitgo/src';
7+
import { omniConfig } from './config';
8+
import * as superagent from 'superagent';
9+
10+
const RECEIVE_ADDRESS = '';
11+
const SEND_ADDRESS = '';
12+
const NETWORK = omniConfig.coin === 'tbtc4/' ? 'testnet4' : '';
13+
const AMOUNT = 1234;
14+
const ASSET_ID = 31;
15+
16+
async function getWallet() {
17+
return await omniConfig.sdk.coin(omniConfig.coin).wallets().get({ id: omniConfig.walletId });
18+
}
19+
20+
function strToHex(s: string) {
21+
return (
22+
s
23+
.split('')
24+
.map((c) => c.charCodeAt(0).toString(16))
25+
.join('') + '0'
26+
);
27+
}
28+
29+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
30+
async function mintOmniAsset(wallet: Wallet, address: string, feeRate = 20_000) {
31+
const transactionVersion = '0000';
32+
const trnasactionType = (50).toString(16).padStart(4, '0');
33+
const ecoSystem = '02';
34+
const propertyType = (2).toString(16).padStart(4, '0');
35+
const previousPropertyID = '00000000';
36+
const category = strToHex('Other\0');
37+
const subCategory = strToHex('Other\0');
38+
const propertyTitle = strToHex('Testcoin\0');
39+
const propertyURL = strToHex('https://example.com\0');
40+
const propertyData = strToHex('\0');
41+
const amount = (100000 * 10**8).toString(16).padStart(16, '0');
42+
43+
const res = await superagent.get(`https://mempool.space/${NETWORK}api/address/${address}/utxo`);
44+
const unspent = res.body[0];
45+
const unspent_id = unspent.txid + ':' + unspent.vout;
46+
47+
const omniScript = [
48+
'6f6d6e69', // omni
49+
transactionVersion,
50+
trnasactionType,
51+
ecoSystem,
52+
propertyType,
53+
previousPropertyID,
54+
category,
55+
subCategory,
56+
propertyTitle,
57+
propertyURL,
58+
propertyData,
59+
amount,
60+
].join('');
61+
62+
// scriptPubkey: op_return omni simple_send tether amount
63+
const script =
64+
'scriptPubkey:' +
65+
[
66+
'6a', // op_return
67+
(omniScript.length / 2).toString(16).padStart(2, '0'),
68+
omniScript,
69+
].join('');
70+
const tx = await wallet.sendMany({
71+
recipients: [
72+
{
73+
amount: '0',
74+
address: script,
75+
},
76+
],
77+
isReplaceableByFee: true,
78+
feeRate,
79+
walletPassphrase: omniConfig.walletPassphrase,
80+
changeAddress: address,
81+
unspents: [unspent_id],
82+
});
83+
console.log('Omni asset created: ', tx);
84+
}
85+
86+
async function sendOmniAsset(
87+
wallet: Wallet,
88+
receiver: string,
89+
sender: string,
90+
amountMicroCents: number,
91+
assetId = 31,
92+
feeRate = 20_000
93+
) {
94+
// convert amountMicroCents to hex string of length 16
95+
const amountHex = amountMicroCents.toString(16).padStart(16, '0');
96+
97+
const assetHex = assetId.toString(16).padStart(8, '0');
98+
99+
const res = await superagent.get(`https://mempool.space/${NETWORK}/api/address/${sender}/utxo`);
100+
const unspent = res.body[0];
101+
const unspent_id = unspent.txid + ':' + unspent.vout;
102+
103+
// scriptPubkey: op_return omni simple_send tether amount
104+
const script = ('scriptPubkey: 6a14 6f6d6e69 00000000' + assetHex + amountHex).split(' ').join('');
105+
const tx = await wallet.sendMany({
106+
recipients: [
107+
{
108+
amount: '546',
109+
address: receiver,
110+
},
111+
{
112+
amount: '0',
113+
address: script,
114+
},
115+
],
116+
isReplaceableByFee: true,
117+
feeRate,
118+
walletPassphrase: omniConfig.walletPassphrase,
119+
changeAddress: sender,
120+
unspents: [unspent_id],
121+
});
122+
console.log('Omni asset sent: ', tx);
123+
}
124+
125+
/*
126+
* Usage: npx ts-node btc/omni/index.ts
127+
* */
128+
async function main() {
129+
console.log('Starting...');
130+
131+
const feeRateRes = await superagent.get(`https://mempool.space/${NETWORK}api/v1/fees/recommended`);
132+
const feeRate = feeRateRes.body.fastestFee;
133+
134+
const wallet = await getWallet();
135+
// we multiply feeRate by 1000 because mempool returns sat/vB and BitGo uses sat/kvB
136+
await sendOmniAsset(wallet, RECEIVE_ADDRESS, SEND_ADDRESS, AMOUNT, ASSET_ID, feeRate * 1000);
137+
}
138+
139+
main().catch((e) => {
140+
console.error(e);
141+
process.exit(1);
142+
});

0 commit comments

Comments
 (0)