Skip to content

Commit d4d7362

Browse files
author
Neo
authored
Merge pull request #2 from MixinNetwork/feature/nft-support
nft support test ok
2 parents b894b0b + a70af3c commit d4d7362

File tree

14 files changed

+494
-303
lines changed

14 files changed

+494
-303
lines changed

example/nft.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
const { Client } = require('mixin-node-sdk') // >= 3.0.13
2+
const keystore = require('./keystore.json')
3+
const { v4: uuid } = require('uuid')
4+
const client = new Client(keystore)
5+
const isMint = true
6+
7+
async function main() {
8+
if (isMint) {
9+
const id = uuid()
10+
const tr = client.newMintCollectibleTransferInput({
11+
trace_id: id,
12+
collection_id: id,
13+
token_id: id,
14+
content: "test"
15+
})
16+
const payment = await client.verifyPayment(tr)
17+
console.log("mint collectibles", id, "mixin://codes/" + payment.code_id)
18+
return
19+
}
20+
const outputs = await client.readCollectibleOutputs([client.keystore.client_id], 1)
21+
console.log(outputs)
22+
23+
outputs.forEach(async output => {
24+
switch (output.state) {
25+
case "unspent":
26+
const token = await client.readCollectibleToken(ctx, output.token_id)
27+
handleUnspentOutput(output, token)
28+
break
29+
case "signed":
30+
handleSignedOutput(output)
31+
break
32+
}
33+
})
34+
}
35+
36+
async function handleUnspentOutput(output, token) {
37+
console.log(`handle unspent output ${output.output_id} ${token.token_id}`)
38+
const receivers = ["e8e8cd79-cd40-4796-8c54-3a13cfe50115"]
39+
const signedTx = await client.makeCollectibleTransactionRaw({
40+
output,
41+
token,
42+
receivers,
43+
threshold: 1
44+
})
45+
const createRes = await client.createCollectibleRequest("sign", signedTx)
46+
console.log("create collectible...", req)
47+
const signRes = await client.signCollectibleRequest(createRes.request_id)
48+
console.log("sign finished...", signRes)
49+
}
50+
51+
async function handleSignedOutput(output) {
52+
console.log(`handle signed output ${output.output_id}`)
53+
const res = await sendExternalProxy("sendrawtransaction", output.signed_tx)
54+
console.log("send raw transaction finished...", res)
55+
}
56+
main()

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mixin-node-sdk",
3-
"version": "3.0.12",
3+
"version": "3.0.13",
44
"license": "MIT",
55
"main": "dist/index.js",
66
"typings": "dist/index.d.ts",

src/client/collectibles.ts

Lines changed: 67 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,86 @@
1-
import { SHA3 } from 'sha3'
21
import { AxiosInstance } from "axios"
3-
import { getSignPIN } from "../mixin/sign"
4-
import { BigNumber } from 'bignumber.js'
52
import {
6-
Keystore,
7-
CollectiblesClientRequest, CollectiblesRequest, CollectiblesUTXO,
8-
CollectiblesAction, RawTransactionInput, Transaction, GhostInput, GhostKeys,
3+
Keystore, TransactionInput, GhostInput, GhostKeys,
4+
CollectiblesClientRequest, CollectiblesParams, CollectibleToken, RawCollectibleInput, Transaction, CollectibleAction, CollectibleRequest, CollectibleOutput,
95
} from "../types"
10-
import { dumpTransaction } from '../mixin/dump_transacion'
11-
12-
const TxVersion = 0x02
13-
14-
const OperatorSum = 0xfe
15-
const OperatorCmp = 0xff
6+
import { DumpOutputFromGhostKey, dumpTransaction } from '../mixin/dump_transacion'
7+
import { hashMember } from '../mixin/tools'
8+
import { TxVersion } from '../mixin/encoder'
9+
import { getSignPIN } from '../mixin/sign'
10+
import { buildMintCollectibleMemo } from '../mixin/nfo'
1611

12+
const MintAssetID = "c94ac88f-4671-3976-b60a-09064f1811e8"
13+
const MintMinimumCost = "0.001"
14+
const GroupMembers = [
15+
"4b188942-9fb0-4b99-b4be-e741a06d1ebf",
16+
"dd655520-c919-4349-822f-af92fabdbdf4",
17+
"047061e6-496d-4c35-b06b-b0424a8a400d",
18+
"acf65344-c778-41ee-bacb-eb546bacfb9f",
19+
"a51006d0-146b-4b32-a2ce-7defbf0d7735",
20+
"cf4abd9c-2cfa-4b5a-b1bd-e2b61a83fabd",
21+
"50115496-7247-4e2c-857b-ec8680756bee",
22+
]
23+
const GroupThreshold = 5
1724
export class CollectiblesClient implements CollectiblesClientRequest {
1825
keystore!: Keystore
1926
request!: AxiosInstance
20-
readGhostKeys!: (receivers: string[], index: number) => Promise<GhostKeys>
2127
batchReadGhostKeys!: (inputs: GhostInput[]) => Promise<GhostKeys[]>
22-
23-
readCollectible(id: string): Promise<CollectiblesUTXO[]> {
24-
return this.request.get(`/collectibles/tokens/${id}`)
28+
newMintCollectibleTransferInput(p: CollectiblesParams): TransactionInput {
29+
const { trace_id, collection_id, token_id, content } = p
30+
if (!trace_id || !collection_id || !token_id || !content) throw new Error("Missing parameters")
31+
let input: TransactionInput = {
32+
asset_id: MintAssetID,
33+
amount: MintMinimumCost,
34+
trace_id,
35+
memo: buildMintCollectibleMemo(collection_id, token_id, content),
36+
opponent_multisig: {
37+
receivers: GroupMembers,
38+
threshold: GroupThreshold,
39+
}
40+
}
41+
return input
42+
}
43+
readCollectibleToken(id: string): Promise<CollectibleToken> {
44+
return this.request.get(`/collectibles/tokens/` + id)
2545
}
26-
readCollectibleOutputs(members: string[], threshold: number, offset: string, limit: number): Promise<CollectiblesUTXO[]> {
27-
if (members.length > 0 && threshold < 1 || threshold > members.length) return Promise.reject(new Error("Invalid threshold or members"))
28-
const params: any = { threshold: Number(threshold), offset, limit }
29-
params.members = hashMember(members)
30-
return this.request.get(`/collectibles/outputs`, { params })
46+
readCollectibleOutputs(_members: string[], threshold: number, offset: string, limit: number): Promise<CollectibleOutput[]> {
47+
const members = hashMember(_members)
48+
return this.request.get(`/collectibles/outputs`, { params: { members, threshold, offset, limit } })
49+
}
50+
async makeCollectibleTransactionRaw(txInput: RawCollectibleInput): Promise<string> {
51+
const { token, output, receivers, threshold } = txInput
52+
const tx: Transaction = {
53+
version: TxVersion,
54+
asset: token.mixin_id!,
55+
extra: token.nfo!,
56+
inputs: [
57+
{
58+
hash: output.transaction_hash!,
59+
index: output.output_index!
60+
}
61+
]
62+
}
63+
const ghostInputs = await this.batchReadGhostKeys([{
64+
receivers,
65+
index: 0,
66+
hint: output.output_id!
67+
}])
68+
tx.outputs = [DumpOutputFromGhostKey(ghostInputs[0], output.amount!, threshold)]
69+
return dumpTransaction(tx)
3170
}
32-
createCollectible(action: CollectiblesAction, raw: string): Promise<CollectiblesRequest> {
71+
createCollectibleRequest(action: CollectibleAction, raw: string): Promise<CollectibleRequest> {
3372
return this.request.post(`/collectibles/requests`, { action, raw })
3473
}
35-
signCollectible(request_id: string, pin?: string): Promise<CollectiblesRequest> {
74+
signCollectibleRequest(requestId: string, pin?: string): Promise<CollectibleRequest> {
3675
pin = getSignPIN(this.keystore, pin)
37-
return this.request.post(`/collectibles/requests/${request_id}/sign`, { pin })
76+
return this.request.post(`/collectibles/requests/${requestId}/sign`, { pin })
3877
}
39-
cancelCollectible(request_id: string): Promise<void> {
40-
return this.request.post(`/collectibles/requests/${request_id}/cancel`)
78+
cancelCollectibleRequest(requestId: string): Promise<void> {
79+
return this.request.post(`/collectibles/requests/${requestId}/cancel`)
4180
}
42-
unlockCollectible(request_id: string, pin: string): Promise<void> {
81+
unlockCollectibleRequest(requestId: string, pin?: string): Promise<void> {
4382
pin = getSignPIN(this.keystore, pin)
44-
return this.request.post(`/collectibles/requests/${request_id}/unlock`, { pin })
83+
return this.request.post(`/collectibles/requests/${requestId}/unlock`, { pin })
4584
}
46-
async makeCollectiblesTransaction(txInput: RawTransactionInput): Promise<string> {
47-
// validate ...
48-
let { inputs, memo, outputs } = txInput
49-
const tx: Transaction = {
50-
version: TxVersion,
51-
asset: newHash(inputs[0].asset_id),
52-
extra: Buffer.from(memo).toString('base64'),
53-
inputs: [],
54-
outputs: []
55-
}
56-
// add input
57-
for (const input of inputs) {
58-
tx.inputs!.push({
59-
hash: input.transaction_hash,
60-
index: input.output_index
61-
})
62-
}
63-
let change = inputs.reduce((sum, input) => sum.plus(input.amount), new BigNumber(0))
64-
for (const output of outputs) change = change.minus(output.amount)
65-
if (change.isGreaterThan(0)) outputs.push({ receivers: inputs[0].members, threshold: inputs[0].threshold, amount: change.toString() })
66-
const ghostInputs: GhostInput[] = []
67-
outputs.forEach((output, idx) => ghostInputs.push({ receivers: output.receivers, index: idx, hint: txInput.hint }))
68-
// get ghost keys
69-
let ghosts = await this.batchReadGhostKeys(ghostInputs)
70-
outputs.forEach((output, idx) => {
71-
const { mask, keys } = ghosts[idx]
72-
tx.outputs!.push({
73-
mask,
74-
keys,
75-
amount: Number(output.amount).toFixed(8),
76-
script: Buffer.from([OperatorCmp, OperatorSum, output.threshold]).toString('hex')
77-
})
78-
})
79-
return dumpTransaction(tx)
80-
}
81-
}
82-
83-
function hashMember(ids: string[]) {
84-
ids = ids.sort((a, b) => a > b ? 1 : -1)
85-
return newHash(ids.join(''))
8685
}
8786

88-
function newHash(str: string) {
89-
return new SHA3(256).update(str).digest('hex')
90-
}

src/client/index.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
AppClientRequest, UpdateAppRequest, App, FavoriteApp,
1212
AssetClientRequest, Asset, ExchangeRate, NetworkTicker,
1313
Attachment, AttachmentClientRequest,
14-
CollectiblesUTXO, CollectiblesRequest,
14+
CollectiblesParams, CollectibleToken, CollectibleAction, CollectibleRequest, CollectibleOutput, RawCollectibleInput,
1515
ConversationClientRequest, ConversationCreateParmas, Conversation, ConversationUpdateParams, Participant, ConversationAction,
1616
MessageClientRequest, AcknowledgementRequest, MessageRequest, MessageView,
1717
ImageMessage, DataMessage, StickerMessage, ContactMesage, AppCardMessage, AudioMessage, LiveMessage, LocationMessage, VideoMessage, AppButtonMessage, RecallMessage,
@@ -80,14 +80,15 @@ export class Client implements
8080
uploadFile!: (file: File) => Promise<Attachment>
8181

8282
// Collectibles...
83-
84-
readCollectible!: (id: string) => Promise<CollectiblesUTXO[]>
85-
readCollectibleOutputs!: (members: string[], threshold: number, offset: string, limit: number) => Promise<CollectiblesUTXO[]>
86-
createCollectible!: (action: string, raw: string) => Promise<CollectiblesRequest>
87-
signCollectible!: (request_id: string, pin: string) => Promise<CollectiblesRequest>
88-
cancelCollectible!: (request_id: string) => Promise<void>
89-
unlockCollectible!: (request_id: string, pin: string) => Promise<void>
90-
makeCollectiblesTransaction!: (txInput: RawTransactionInput) => Promise<string>
83+
newMintCollectibleTransferInput!: (p: CollectiblesParams) => TransactionInput
84+
85+
readCollectibleToken!: (id: string) => Promise<CollectibleToken>
86+
readCollectibleOutputs!: (members: string[], threshold: number, offset: string, limit: number) => Promise<CollectibleOutput[]>
87+
makeCollectibleTransactionRaw!: (txInput: RawCollectibleInput) => Promise<string>
88+
createCollectibleRequest!: (action: CollectibleAction, raw: string) => Promise<CollectibleRequest>
89+
signCollectibleRequest!: (requestId: string, pin?: string) => Promise<CollectibleRequest>
90+
cancelCollectibleRequest!: (requestId: string) => Promise<void>
91+
unlockCollectibleRequest!: (requestId: string, pin?: string) => Promise<void>
9192

9293
// Conversation...
9394
createConversation!: (params: ConversationCreateParmas) => Promise<Conversation>

src/client/multisigs.ts

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { SHA3 } from 'sha3'
21
import { AxiosInstance } from "axios"
32
import { getSignPIN } from "../mixin/sign"
43
import { BigNumber } from 'bignumber.js'
@@ -7,12 +6,9 @@ import {
76
MultisigClientRequest, MultisigRequest, MultisigUTXO,
87
MultisigAction, RawTransactionInput, Transaction, GhostInput, GhostKeys,
98
} from "../types"
10-
import { dumpTransaction } from '../mixin/dump_transacion'
11-
12-
const TxVersion = 0x02
13-
14-
const OperatorSum = 0xfe
15-
const OperatorCmp = 0xff
9+
import { DumpOutputFromGhostKey, dumpTransaction } from '../mixin/dump_transacion'
10+
import { hashMember, newHash } from '../mixin/tools'
11+
import { TxVersion } from '../mixin/encoder'
1612

1713
export class MultisigsClient implements MultisigClientRequest {
1814
keystore!: Keystore
@@ -27,18 +23,18 @@ export class MultisigsClient implements MultisigClientRequest {
2723
return this.request.get(`/multisigs/outputs`, { params })
2824
}
2925
createMultisig(action: MultisigAction, raw: string): Promise<MultisigRequest> {
30-
return this.request.post(`/multisigs`, { action, raw })
26+
return this.request.post(`/multisigs/requests`, { action, raw })
3127
}
3228
signMultisig(request_id: string, pin?: string): Promise<MultisigRequest> {
3329
pin = getSignPIN(this.keystore, pin)
34-
return this.request.post(`/multisigs/${request_id}/sign`, { pin })
30+
return this.request.post(`/multisigs/requests/${request_id}/sign`, { pin })
3531
}
3632
cancelMultisig(request_id: string): Promise<void> {
37-
return this.request.post(`/multisigs/${request_id}/cancel`)
33+
return this.request.post(`/multisigs/requests/${request_id}/cancel`)
3834
}
3935
unlockMultisig(request_id: string, pin: string): Promise<void> {
4036
pin = getSignPIN(this.keystore, pin)
41-
return this.request.post(`/multisigs/${request_id}/unlock`, { pin })
37+
return this.request.post(`/multisigs/requests/${request_id}/unlock`, { pin })
4238
}
4339
readGhostKeys(receivers: string[], index: number): Promise<GhostKeys> {
4440
return this.request.post("/outputs", { receivers, index, hint: "" })
@@ -70,24 +66,9 @@ export class MultisigsClient implements MultisigClientRequest {
7066
outputs.forEach((output, idx) => ghostInputs.push({ receivers: output.receivers, index: idx, hint: txInput.hint }))
7167
// get ghost keys
7268
let ghosts = await this.batchReadGhostKeys(ghostInputs)
73-
outputs.forEach((output, idx) => {
74-
const { mask, keys } = ghosts[idx]
75-
tx.outputs!.push({
76-
mask,
77-
keys,
78-
amount: Number(output.amount).toFixed(8),
79-
script: Buffer.from([OperatorCmp, OperatorSum, output.threshold]).toString('hex')
80-
})
81-
})
69+
outputs.forEach((output, idx) =>
70+
tx.outputs!.push(DumpOutputFromGhostKey(ghosts[idx], output.amount, output.threshold))
71+
)
8272
return dumpTransaction(tx)
8373
}
8474
}
85-
86-
function hashMember(ids: string[]) {
87-
ids = ids.sort((a, b) => a > b ? 1 : -1)
88-
return newHash(ids.join(''))
89-
}
90-
91-
function newHash(str: string) {
92-
return new SHA3(256).update(str).digest('hex')
93-
}

src/client/network.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ export const searchNetworkAsset = (assetNameOrSymbol: string): Promise<Asset[]>
2020

2121
export const readExternalAddressesCheck = (params: SnapshotQuery): Promise<boolean> => mixinRequest.get(`/external/addresses/check`, { params })
2222

23-
export const readNetworkTicker = (asset_id: string, offset?: string): Promise<NetworkTicker> => mixinRequest.get(`/network/ticker`, { params: { asset: asset_id, offset } })
23+
export const readNetworkTicker = (asset_id: string, offset?: string): Promise<NetworkTicker> => mixinRequest.get(`/network/ticker`, { params: { asset: asset_id, offset } })
24+
25+
export const sendExternalProxy = (method: string, params: any[]): Promise<any> => mixinRequest.post(`/external/proxy`, { method, params })

0 commit comments

Comments
 (0)