@@ -18,11 +18,12 @@ import {
18
18
StacksTestnet ,
19
19
standardPrincipalCV ,
20
20
TransactionSigner ,
21
+ UnsignedMultiSigTokenTransferOptions ,
21
22
UnsignedTokenTransferOptions ,
22
23
} from '@blockstack/stacks-transactions' ;
23
24
import * as BN from 'bn.js' ;
24
25
import { getCoreNodeEndpoint , StacksCoreRpcClient } from '../core-rpc/client' ;
25
- import { bufferToHexPrefixString } from '../helpers' ;
26
+ import { bufferToHexPrefixString , digestSha512_256 } from '../helpers' ;
26
27
import {
27
28
RosettaConstructionCombineRequest ,
28
29
RosettaConstructionCombineResponse ,
@@ -39,8 +40,7 @@ import {
39
40
} from '@blockstack/stacks-blockchain-api-types' ;
40
41
import { RosettaConstants , RosettaErrors } from '../api/rosetta-constants' ;
41
42
import { GetStacksTestnetNetwork , testnetKeys } from '../api/routes/debug' ;
42
- import { getSignature } from '../rosetta-helpers' ;
43
- import { cloneDeep } from '@blockstack/stacks-transactions/lib/utils' ;
43
+ import { getOptionsFromOperations , getSignature } from '../rosetta-helpers' ;
44
44
45
45
describe ( 'Rosetta API' , ( ) => {
46
46
let db : PgDataStore ;
@@ -1135,7 +1135,12 @@ describe('Rosetta API', () => {
1135
1135
expect ( JSON . parse ( result . text ) ) . toEqual ( expectedResponse ) ;
1136
1136
} ) ;
1137
1137
1138
- test ( 'payloads success' , async ( ) => {
1138
+ test ( 'payloads single sign success' , async ( ) => {
1139
+ const publicKey = publicKeyToString ( pubKeyfromPrivKey ( testnetKeys [ 0 ] . secretKey ) ) ;
1140
+ const sender = testnetKeys [ 0 ] . stacksAddress ;
1141
+ const recipient = testnetKeys [ 1 ] . stacksAddress ;
1142
+ const fee = '-180' ;
1143
+
1139
1144
const request : RosettaConstructionPayloadsRequest = {
1140
1145
network_identifier : {
1141
1146
blockchain : 'stacks' ,
@@ -1151,11 +1156,11 @@ describe('Rosetta API', () => {
1151
1156
type : 'fee' ,
1152
1157
status : 'success' ,
1153
1158
account : {
1154
- address : 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6' ,
1159
+ address : sender ,
1155
1160
metadata : { } ,
1156
1161
} ,
1157
1162
amount : {
1158
- value : '-180' ,
1163
+ value : fee ,
1159
1164
currency : {
1160
1165
symbol : 'STX' ,
1161
1166
decimals : 6 ,
@@ -1172,7 +1177,7 @@ describe('Rosetta API', () => {
1172
1177
type : 'token_transfer' ,
1173
1178
status : 'success' ,
1174
1179
account : {
1175
- address : 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6' ,
1180
+ address : sender ,
1176
1181
metadata : { } ,
1177
1182
} ,
1178
1183
amount : {
@@ -1193,7 +1198,7 @@ describe('Rosetta API', () => {
1193
1198
type : 'token_transfer' ,
1194
1199
status : 'success' ,
1195
1200
account : {
1196
- address : 'STDE7Y8HV3RX8VBM2TZVWJTS7ZA1XB0SSC3NEVH0' ,
1201
+ address : recipient ,
1197
1202
metadata : { } ,
1198
1203
} ,
1199
1204
amount : {
@@ -1208,12 +1213,27 @@ describe('Rosetta API', () => {
1208
1213
] ,
1209
1214
public_keys : [
1210
1215
{
1211
- hex_bytes : '025c13b2fc2261956d8a4ad07d481b1a3b2cbf93a24f992249a61c3a1c4de79c51' ,
1216
+ hex_bytes : publicKey ,
1212
1217
curve_type : 'secp256k1' ,
1213
1218
} ,
1214
1219
] ,
1215
1220
} ;
1216
1221
1222
+ const accountInfo = await new StacksCoreRpcClient ( ) . getAccount ( sender ) ;
1223
+
1224
+ const tokenTransferOptions : UnsignedTokenTransferOptions = {
1225
+ recipient : recipient ,
1226
+ amount : new BN ( '500000' ) ,
1227
+ fee : new BN ( fee ) ,
1228
+ publicKey : publicKey ,
1229
+ network : GetStacksTestnetNetwork ( ) ,
1230
+ nonce : accountInfo . nonce ? new BN ( accountInfo . nonce ) : new BN ( 0 ) ,
1231
+ } ;
1232
+
1233
+ const transaction = await makeUnsignedSTXTokenTransfer ( tokenTransferOptions ) ;
1234
+ const unsignedTransaction = transaction . serialize ( ) ;
1235
+ const hexBytes = digestSha512_256 ( unsignedTransaction ) . toString ( 'hex' ) ;
1236
+
1217
1237
const result = await supertest ( api . server )
1218
1238
. post ( `/rosetta/v1/construction/payloads` )
1219
1239
. send ( request ) ;
@@ -1222,12 +1242,138 @@ describe('Rosetta API', () => {
1222
1242
expect ( result . type ) . toBe ( 'application/json' ) ;
1223
1243
1224
1244
const expectedResponse = {
1225
- unsigned_transaction :
1226
- '80800000000400539886f96611ba3ba6cef9618f8c78118b37c5be000000000000000000000000000000b400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003020000000000051a1ae3f911d8f1d46d7416bfbe4b593fd41eac19cb000000000007a12000000000000000000000000000000000000000000000000000000000000000000000' ,
1245
+ unsigned_transaction : unsignedTransaction . toString ( 'hex' ) ,
1227
1246
payloads : [
1228
1247
{
1229
- address : 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6' ,
1230
- hex_bytes : '0xf1e432494d509577c5468a8cad70d957942e2671f299340a20f65992a4bfa221' ,
1248
+ address : sender ,
1249
+ hex_bytes : '0x' + hexBytes ,
1250
+ signature_type : 'ecdsa' ,
1251
+ } ,
1252
+ ] ,
1253
+ } ;
1254
+
1255
+ expect ( JSON . parse ( result . text ) ) . toEqual ( expectedResponse ) ;
1256
+ } ) ;
1257
+
1258
+ test ( 'payloads multi sign success' , async ( ) => {
1259
+ const publicKey1 = publicKeyToString ( pubKeyfromPrivKey ( testnetKeys [ 0 ] . secretKey ) ) ;
1260
+ const publicKey2 = publicKeyToString ( pubKeyfromPrivKey ( testnetKeys [ 1 ] . secretKey ) ) ;
1261
+
1262
+ const sender = testnetKeys [ 0 ] . stacksAddress ;
1263
+ const recipient = testnetKeys [ 1 ] . stacksAddress ;
1264
+ const fee = '-180' ;
1265
+
1266
+ const request : RosettaConstructionPayloadsRequest = {
1267
+ network_identifier : {
1268
+ blockchain : 'stacks' ,
1269
+ network : 'testnet' ,
1270
+ } ,
1271
+ operations : [
1272
+ {
1273
+ operation_identifier : {
1274
+ index : 0 ,
1275
+ network_index : 0 ,
1276
+ } ,
1277
+ related_operations : [ ] ,
1278
+ type : 'fee' ,
1279
+ status : 'success' ,
1280
+ account : {
1281
+ address : sender ,
1282
+ metadata : { } ,
1283
+ } ,
1284
+ amount : {
1285
+ value : fee ,
1286
+ currency : {
1287
+ symbol : 'STX' ,
1288
+ decimals : 6 ,
1289
+ } ,
1290
+ metadata : { } ,
1291
+ } ,
1292
+ } ,
1293
+ {
1294
+ operation_identifier : {
1295
+ index : 1 ,
1296
+ network_index : 0 ,
1297
+ } ,
1298
+ related_operations : [ ] ,
1299
+ type : 'token_transfer' ,
1300
+ status : 'success' ,
1301
+ account : {
1302
+ address : sender ,
1303
+ metadata : { } ,
1304
+ } ,
1305
+ amount : {
1306
+ value : '-500000' ,
1307
+ currency : {
1308
+ symbol : 'STX' ,
1309
+ decimals : 6 ,
1310
+ } ,
1311
+ metadata : { } ,
1312
+ } ,
1313
+ } ,
1314
+ {
1315
+ operation_identifier : {
1316
+ index : 2 ,
1317
+ network_index : 0 ,
1318
+ } ,
1319
+ related_operations : [ ] ,
1320
+ type : 'token_transfer' ,
1321
+ status : 'success' ,
1322
+ account : {
1323
+ address : recipient ,
1324
+ metadata : { } ,
1325
+ } ,
1326
+ amount : {
1327
+ value : '500000' ,
1328
+ currency : {
1329
+ symbol : 'STX' ,
1330
+ decimals : 6 ,
1331
+ } ,
1332
+ metadata : { } ,
1333
+ } ,
1334
+ } ,
1335
+ ] ,
1336
+ public_keys : [
1337
+ {
1338
+ hex_bytes : publicKey1 ,
1339
+ curve_type : 'secp256k1' ,
1340
+ } ,
1341
+ {
1342
+ hex_bytes : publicKey2 ,
1343
+ curve_type : 'secp256k1' ,
1344
+ } ,
1345
+ ] ,
1346
+ } ;
1347
+
1348
+ const accountInfo = await new StacksCoreRpcClient ( ) . getAccount ( sender ) ;
1349
+
1350
+ const tokenTransferOptions : UnsignedMultiSigTokenTransferOptions = {
1351
+ recipient : recipient ,
1352
+ amount : new BN ( '500000' ) ,
1353
+ fee : new BN ( fee ) ,
1354
+ publicKeys : [ publicKey1 , publicKey2 ] ,
1355
+ numSignatures : 2 ,
1356
+ network : GetStacksTestnetNetwork ( ) ,
1357
+ nonce : accountInfo . nonce ? new BN ( accountInfo . nonce ) : new BN ( 0 ) ,
1358
+ } ;
1359
+
1360
+ const transaction = await makeUnsignedSTXTokenTransfer ( tokenTransferOptions ) ;
1361
+ const unsignedTransaction = transaction . serialize ( ) ;
1362
+ const hexBytes = digestSha512_256 ( unsignedTransaction ) . toString ( 'hex' ) ;
1363
+
1364
+ const result = await supertest ( api . server )
1365
+ . post ( `/rosetta/v1/construction/payloads` )
1366
+ . send ( request ) ;
1367
+
1368
+ expect ( result . status ) . toBe ( 200 ) ;
1369
+ expect ( result . type ) . toBe ( 'application/json' ) ;
1370
+
1371
+ const expectedResponse = {
1372
+ unsigned_transaction : unsignedTransaction . toString ( 'hex' ) ,
1373
+ payloads : [
1374
+ {
1375
+ address : sender ,
1376
+ hex_bytes : '0x' + hexBytes ,
1231
1377
signature_type : 'ecdsa' ,
1232
1378
} ,
1233
1379
] ,
@@ -1412,7 +1558,7 @@ describe('Rosetta API', () => {
1412
1558
expect ( JSON . parse ( result . text ) ) . toEqual ( expectedResponse ) ;
1413
1559
} ) ;
1414
1560
1415
- test ( 'combine success' , async ( ) => {
1561
+ test ( 'combine single sign success' , async ( ) => {
1416
1562
const publicKey = publicKeyToString ( pubKeyfromPrivKey ( testnetKeys [ 0 ] . secretKey ) ) ;
1417
1563
1418
1564
const txOptions : UnsignedTokenTransferOptions = {
@@ -1471,6 +1617,67 @@ describe('Rosetta API', () => {
1471
1617
expect ( JSON . parse ( result . text ) ) . toEqual ( expectedResponse ) ;
1472
1618
} ) ;
1473
1619
1620
+ test ( 'combine multi sign success' , async ( ) => {
1621
+ const publicKey1 = publicKeyToString ( pubKeyfromPrivKey ( testnetKeys [ 0 ] . secretKey ) ) ;
1622
+ const publicKey2 = publicKeyToString ( pubKeyfromPrivKey ( testnetKeys [ 1 ] . secretKey ) ) ;
1623
+
1624
+ const txOptions : UnsignedMultiSigTokenTransferOptions = {
1625
+ publicKeys : [ publicKey1 , publicKey2 ] ,
1626
+ numSignatures : 2 ,
1627
+ recipient : standardPrincipalCV ( testnetKeys [ 1 ] . stacksAddress ) ,
1628
+ amount : new BigNum ( 12345 ) ,
1629
+ network : GetStacksTestnetNetwork ( ) ,
1630
+ memo : 'test memo' ,
1631
+ nonce : new BigNum ( 0 ) ,
1632
+ fee : new BigNum ( 200 ) ,
1633
+ } ;
1634
+
1635
+ const unsignedTransaction = await makeUnsignedSTXTokenTransfer ( txOptions ) ;
1636
+ const unsignedSerializedTx = unsignedTransaction . serialize ( ) . toString ( 'hex' ) ;
1637
+
1638
+ const signer = new TransactionSigner ( unsignedTransaction ) ;
1639
+ signer . signOrigin ( createStacksPrivateKey ( testnetKeys [ 0 ] . secretKey ) ) ;
1640
+ const signedSerializedTx = unsignedTransaction . serialize ( ) . toString ( 'hex' ) ;
1641
+
1642
+ const signature = getSignature ( unsignedTransaction ) ;
1643
+ if ( ! signature ) return ;
1644
+
1645
+ const request : RosettaConstructionCombineRequest = {
1646
+ network_identifier : {
1647
+ blockchain : 'stacks' ,
1648
+ network : 'testnet' ,
1649
+ } ,
1650
+ unsigned_transaction : unsignedSerializedTx ,
1651
+ signatures : [
1652
+ {
1653
+ signing_payload : {
1654
+ hex_bytes : signature . data ,
1655
+ signature_type : 'ecdsa' ,
1656
+ } ,
1657
+ public_key : {
1658
+ hex_bytes : publicKey1 ,
1659
+ curve_type : 'secp256k1' ,
1660
+ } ,
1661
+ signature_type : 'ecdsa' ,
1662
+ hex_bytes : signature . data ,
1663
+ } ,
1664
+ ] ,
1665
+ } ;
1666
+
1667
+ const result = await supertest ( api . server )
1668
+ . post ( `/rosetta/v1/construction/combine` )
1669
+ . send ( request ) ;
1670
+
1671
+ expect ( result . status ) . toBe ( 200 ) ;
1672
+ expect ( result . type ) . toBe ( 'application/json' ) ;
1673
+
1674
+ const expectedResponse : RosettaConstructionCombineResponse = {
1675
+ signed_transaction : signedSerializedTx ,
1676
+ } ;
1677
+
1678
+ expect ( JSON . parse ( result . text ) ) . toEqual ( expectedResponse ) ;
1679
+ } ) ;
1680
+
1474
1681
test ( 'combine invalid transaction' , async ( ) => {
1475
1682
const request : RosettaConstructionCombineRequest = {
1476
1683
network_identifier : {
0 commit comments