mirror of https://github.com/certusone/oyster.git
Merge branch 'feature/m' of https://github.com/solana-labs/oyster into feature/m
This commit is contained in:
commit
6183122b6a
|
@ -1,5 +1,4 @@
|
|||
import {
|
||||
Account,
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
|
@ -8,6 +7,7 @@ import {
|
|||
import { programIds } from '../utils/ids';
|
||||
import { deserializeBorsh } from './../utils/borsh';
|
||||
import { serialize } from 'borsh';
|
||||
import BN from 'bn.js';
|
||||
|
||||
export const MAX_NAME_LENGTH = 32;
|
||||
|
||||
|
@ -16,12 +16,17 @@ export const MAX_SYMBOL_LENGTH = 10;
|
|||
export const MAX_URI_LENGTH = 200;
|
||||
|
||||
export const MAX_METADATA_LEN =
|
||||
32 + MAX_NAME_LENGTH + MAX_SYMBOL_LENGTH + MAX_URI_LENGTH + 200;
|
||||
1 + 32 + MAX_NAME_LENGTH + MAX_SYMBOL_LENGTH + MAX_URI_LENGTH + 200;
|
||||
|
||||
export const MAX_OWNER_LEN = 32 + 32;
|
||||
export const MAX_NAME_SYMBOL_LEN = 1 + 32 + 8;
|
||||
export const MAX_MASTER_EDITION_KEN = 1 + 9 + 8 + 32;
|
||||
|
||||
export const METADATA_KEY = 0;
|
||||
export const NAME_SYMBOL_KEY = 1;
|
||||
export enum Key {
|
||||
MetadataV1 = 0,
|
||||
NameSymbolTupleV1 = 1,
|
||||
EditionV1 = 2,
|
||||
MasterEditionV1 = 3,
|
||||
}
|
||||
|
||||
export enum MetadataCategory {
|
||||
Audio = 'audio',
|
||||
|
@ -42,9 +47,29 @@ export interface IMetadataExtension {
|
|||
category: MetadataCategory;
|
||||
}
|
||||
|
||||
export class MasterEdition {
|
||||
key: Key;
|
||||
supply: BN;
|
||||
maxSupply?: BN;
|
||||
/// Can be used to mint tokens that give one-time permission to mint a single limited edition.
|
||||
masterMint: PublicKey;
|
||||
|
||||
constructor(args: {
|
||||
key: Key;
|
||||
supply: BN;
|
||||
maxSupply?: BN;
|
||||
/// Can be used to mint tokens that give one-time permission to mint a single limited edition.
|
||||
masterMint: PublicKey;
|
||||
}) {
|
||||
this.key = Key.MasterEditionV1;
|
||||
this.supply = args.supply;
|
||||
this.maxSupply = args.maxSupply;
|
||||
this.masterMint = args.masterMint;
|
||||
}
|
||||
}
|
||||
export class Metadata {
|
||||
key: number;
|
||||
updateAuthority?: PublicKey;
|
||||
key: Key;
|
||||
nonUniqueSpecificUpdateAuthority?: PublicKey;
|
||||
|
||||
mint: PublicKey;
|
||||
name: string;
|
||||
|
@ -54,16 +79,16 @@ export class Metadata {
|
|||
extended?: IMetadataExtension;
|
||||
|
||||
constructor(args: {
|
||||
updateAuthority?: Buffer;
|
||||
mint: Buffer;
|
||||
nonUniqueSpecificUpdateAuthority?: PublicKey;
|
||||
mint: PublicKey;
|
||||
name: string;
|
||||
symbol: string;
|
||||
uri: string;
|
||||
}) {
|
||||
this.key = METADATA_KEY;
|
||||
this.updateAuthority =
|
||||
args.updateAuthority && new PublicKey(args.updateAuthority);
|
||||
this.mint = new PublicKey(args.mint);
|
||||
this.key = Key.MetadataV1;
|
||||
this.nonUniqueSpecificUpdateAuthority =
|
||||
args.nonUniqueSpecificUpdateAuthority;
|
||||
this.mint = args.mint;
|
||||
this.name = args.name;
|
||||
this.symbol = args.symbol;
|
||||
this.uri = args.uri;
|
||||
|
@ -71,12 +96,12 @@ export class Metadata {
|
|||
}
|
||||
|
||||
export class NameSymbolTuple {
|
||||
key: number;
|
||||
key: Key;
|
||||
updateAuthority: PublicKey;
|
||||
metadata: PublicKey;
|
||||
|
||||
constructor(args: { updateAuthority: Buffer; metadata: Buffer }) {
|
||||
this.key = NAME_SYMBOL_KEY;
|
||||
this.key = Key.NameSymbolTupleV1;
|
||||
this.updateAuthority = new PublicKey(args.updateAuthority);
|
||||
this.metadata = new PublicKey(args.metadata);
|
||||
}
|
||||
|
@ -84,7 +109,7 @@ export class NameSymbolTuple {
|
|||
|
||||
class CreateMetadataArgs {
|
||||
instruction: number = 0;
|
||||
allow_duplicates: boolean = false;
|
||||
allowDuplicates: boolean = false;
|
||||
name: string;
|
||||
symbol: string;
|
||||
uri: string;
|
||||
|
@ -93,31 +118,44 @@ class CreateMetadataArgs {
|
|||
name: string;
|
||||
symbol: string;
|
||||
uri: string;
|
||||
allow_duplicates?: boolean;
|
||||
allowDuplicates?: boolean;
|
||||
}) {
|
||||
this.name = args.name;
|
||||
this.symbol = args.symbol;
|
||||
this.uri = args.uri;
|
||||
this.allow_duplicates = !!args.allow_duplicates;
|
||||
this.allowDuplicates = !!args.allowDuplicates;
|
||||
}
|
||||
}
|
||||
class UpdateMetadataArgs {
|
||||
instruction: number = 1;
|
||||
uri: string;
|
||||
// Not used by this app, just required for instruction
|
||||
non_unique_specific_update_authority: number;
|
||||
nonUniqueSpecificUpdateAuthority?: PublicKey;
|
||||
|
||||
constructor(args: { uri: string }) {
|
||||
constructor(args: {
|
||||
uri: string;
|
||||
nonUniqueSpecificUpdateAuthority?: string;
|
||||
}) {
|
||||
this.uri = args.uri;
|
||||
this.non_unique_specific_update_authority = 0;
|
||||
this.nonUniqueSpecificUpdateAuthority = args.nonUniqueSpecificUpdateAuthority
|
||||
? new PublicKey(args.nonUniqueSpecificUpdateAuthority)
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class TransferMetadataArgs {
|
||||
class TransferUpdateAuthorityArgs {
|
||||
instruction: number = 2;
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
class CreateMasterEditionArgs {
|
||||
instruction: number = 3;
|
||||
maxSupply?: BN;
|
||||
constructor(args: { maxSupply?: BN }) {
|
||||
this.maxSupply = args.maxSupply;
|
||||
}
|
||||
}
|
||||
|
||||
export const SCHEMA = new Map<any, any>([
|
||||
[
|
||||
CreateMetadataArgs,
|
||||
|
@ -125,7 +163,7 @@ export const SCHEMA = new Map<any, any>([
|
|||
kind: 'struct',
|
||||
fields: [
|
||||
['instruction', 'u8'],
|
||||
['allow_duplicates', 'u8'],
|
||||
['allowDuplicates', 'u8'],
|
||||
['name', 'string'],
|
||||
['symbol', 'string'],
|
||||
['uri', 'string'],
|
||||
|
@ -139,25 +177,53 @@ export const SCHEMA = new Map<any, any>([
|
|||
fields: [
|
||||
['instruction', 'u8'],
|
||||
['uri', 'string'],
|
||||
['non_unique_specific_update_authority', 'u8'],
|
||||
[
|
||||
'nonUniqueSpecificUpdateAuthority',
|
||||
{ kind: 'option', type: 'pubkey' },
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
TransferMetadataArgs,
|
||||
TransferUpdateAuthorityArgs,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [['instruction', 'u8']],
|
||||
},
|
||||
],
|
||||
[
|
||||
CreateMasterEditionArgs,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['instruction', 'u8'],
|
||||
['maxSupply', { kind: 'option', type: 'u64' }],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
MasterEdition,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['supply', 'u64'],
|
||||
['maxSupply', { kind: 'option', type: 'u64' }],
|
||||
['masterMint', 'publicKey'],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
Metadata,
|
||||
{
|
||||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['allow_duplicates', { kind: 'option', type: 'u8' }],
|
||||
['mint', [32]],
|
||||
[
|
||||
'nonUniqueSpecificUpdateAuthority',
|
||||
{ kind: 'option', type: 'pubkey' },
|
||||
],
|
||||
['mint', 'pubkey'],
|
||||
['name', 'string'],
|
||||
['symbol', 'string'],
|
||||
['uri', 'string'],
|
||||
|
@ -170,8 +236,8 @@ export const SCHEMA = new Map<any, any>([
|
|||
kind: 'struct',
|
||||
fields: [
|
||||
['key', 'u8'],
|
||||
['update_authority', [32]],
|
||||
['metadata', [32]],
|
||||
['updateAuthority', 'pubkey'],
|
||||
['metadata', 'pubkey'],
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -181,37 +247,21 @@ export const decodeMetadata = (buffer: Buffer) => {
|
|||
return deserializeBorsh(SCHEMA, Metadata, buffer) as Metadata;
|
||||
};
|
||||
|
||||
export async function transferMetadata(
|
||||
symbol: string,
|
||||
name: string,
|
||||
export async function transferUpdateAuthority(
|
||||
account: PublicKey,
|
||||
currentUpdateAuthority: PublicKey,
|
||||
newUpdateAuthority: PublicKey,
|
||||
instructions: TransactionInstruction[],
|
||||
signers: Account[],
|
||||
metadataAccount?: PublicKey,
|
||||
metadataOwnerAccount?: PublicKey,
|
||||
) {
|
||||
const metadataProgramId = programIds().metadata;
|
||||
|
||||
metadataOwnerAccount =
|
||||
metadataOwnerAccount ||
|
||||
(
|
||||
await PublicKey.findProgramAddress(
|
||||
[
|
||||
Buffer.from('metadata'),
|
||||
metadataProgramId.toBuffer(),
|
||||
Buffer.from(name),
|
||||
Buffer.from(symbol),
|
||||
],
|
||||
metadataProgramId,
|
||||
)
|
||||
)[0];
|
||||
|
||||
const data = Buffer.from(serialize(SCHEMA, new TransferMetadataArgs()));
|
||||
const data = Buffer.from(
|
||||
serialize(SCHEMA, new TransferUpdateAuthorityArgs()),
|
||||
);
|
||||
|
||||
const keys = [
|
||||
{
|
||||
pubkey: metadataOwnerAccount,
|
||||
pubkey: account,
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
},
|
||||
|
@ -233,20 +283,18 @@ export async function transferMetadata(
|
|||
data: data,
|
||||
}),
|
||||
);
|
||||
|
||||
return [metadataAccount, metadataOwnerAccount];
|
||||
}
|
||||
|
||||
export async function updateMetadata(
|
||||
symbol: string,
|
||||
name: string,
|
||||
uri: string,
|
||||
newNonUniqueSpecificUpdateAuthority: string | undefined,
|
||||
mintKey: PublicKey,
|
||||
updateAuthority: PublicKey,
|
||||
instructions: TransactionInstruction[],
|
||||
signers: Account[],
|
||||
metadataAccount?: PublicKey,
|
||||
metadataOwnerAccount?: PublicKey,
|
||||
nameSymbolAccount?: PublicKey,
|
||||
) {
|
||||
const metadataProgramId = programIds().metadata;
|
||||
|
||||
|
@ -263,8 +311,8 @@ export async function updateMetadata(
|
|||
)
|
||||
)[0];
|
||||
|
||||
metadataOwnerAccount =
|
||||
metadataOwnerAccount ||
|
||||
nameSymbolAccount =
|
||||
nameSymbolAccount ||
|
||||
(
|
||||
await PublicKey.findProgramAddress(
|
||||
[
|
||||
|
@ -277,9 +325,13 @@ export async function updateMetadata(
|
|||
)
|
||||
)[0];
|
||||
|
||||
const value = new UpdateMetadataArgs({ uri });
|
||||
const value = new UpdateMetadataArgs({
|
||||
uri,
|
||||
nonUniqueSpecificUpdateAuthority: !newNonUniqueSpecificUpdateAuthority
|
||||
? undefined
|
||||
: newNonUniqueSpecificUpdateAuthority,
|
||||
});
|
||||
const data = Buffer.from(serialize(SCHEMA, value));
|
||||
|
||||
const keys = [
|
||||
{
|
||||
pubkey: metadataAccount,
|
||||
|
@ -292,7 +344,7 @@ export async function updateMetadata(
|
|||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: metadataOwnerAccount,
|
||||
pubkey: nameSymbolAccount,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
|
@ -305,20 +357,19 @@ export async function updateMetadata(
|
|||
}),
|
||||
);
|
||||
|
||||
return [metadataAccount, metadataOwnerAccount];
|
||||
return [metadataAccount, nameSymbolAccount];
|
||||
}
|
||||
|
||||
export async function createMetadata(
|
||||
symbol: string,
|
||||
name: string,
|
||||
uri: string,
|
||||
allow_duplicates: boolean,
|
||||
allowDuplicates: boolean,
|
||||
updateAuthority: PublicKey,
|
||||
mintKey: PublicKey,
|
||||
mintAuthorityKey: PublicKey,
|
||||
instructions: TransactionInstruction[],
|
||||
payer: PublicKey,
|
||||
signers: Account[],
|
||||
) {
|
||||
const metadataProgramId = programIds().metadata;
|
||||
|
||||
|
@ -333,7 +384,7 @@ export async function createMetadata(
|
|||
)
|
||||
)[0];
|
||||
|
||||
const metadataOwnerAccount = (
|
||||
const nameSymbolAccount = (
|
||||
await PublicKey.findProgramAddress(
|
||||
[
|
||||
Buffer.from('metadata'),
|
||||
|
@ -345,12 +396,12 @@ export async function createMetadata(
|
|||
)
|
||||
)[0];
|
||||
|
||||
const value = new CreateMetadataArgs({ name, symbol, uri, allow_duplicates });
|
||||
const value = new CreateMetadataArgs({ name, symbol, uri, allowDuplicates });
|
||||
const data = Buffer.from(serialize(SCHEMA, value));
|
||||
|
||||
const keys = [
|
||||
{
|
||||
pubkey: metadataOwnerAccount,
|
||||
pubkey: nameSymbolAccount,
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
},
|
||||
|
@ -398,5 +449,122 @@ export async function createMetadata(
|
|||
}),
|
||||
);
|
||||
|
||||
return [metadataAccount, metadataOwnerAccount];
|
||||
return [metadataAccount, nameSymbolAccount];
|
||||
}
|
||||
|
||||
export async function createMasterEdition(
|
||||
name: string,
|
||||
symbol: string,
|
||||
maxSupply: BN | undefined,
|
||||
mintKey: PublicKey,
|
||||
masterMintKey: PublicKey,
|
||||
updateAuthorityKey: PublicKey,
|
||||
mintAuthorityKey: PublicKey,
|
||||
instructions: TransactionInstruction[],
|
||||
payer: PublicKey,
|
||||
) {
|
||||
const metadataProgramId = programIds().metadata;
|
||||
|
||||
const metadataAccount = (
|
||||
await PublicKey.findProgramAddress(
|
||||
[
|
||||
Buffer.from('metadata'),
|
||||
metadataProgramId.toBuffer(),
|
||||
mintKey.toBuffer(),
|
||||
],
|
||||
metadataProgramId,
|
||||
)
|
||||
)[0];
|
||||
|
||||
const nameSymbolAccount = (
|
||||
await PublicKey.findProgramAddress(
|
||||
[
|
||||
Buffer.from('metadata'),
|
||||
metadataProgramId.toBuffer(),
|
||||
Buffer.from(name),
|
||||
Buffer.from(symbol),
|
||||
],
|
||||
metadataProgramId,
|
||||
)
|
||||
)[0];
|
||||
|
||||
const editionAccount = (
|
||||
await PublicKey.findProgramAddress(
|
||||
[
|
||||
Buffer.from('metadata'),
|
||||
metadataProgramId.toBuffer(),
|
||||
mintKey.toBuffer(),
|
||||
Buffer.from('edition'),
|
||||
],
|
||||
metadataProgramId,
|
||||
)
|
||||
)[0];
|
||||
|
||||
const value = new CreateMasterEditionArgs({ maxSupply });
|
||||
const data = Buffer.from(serialize(SCHEMA, value));
|
||||
|
||||
const keys = [
|
||||
{
|
||||
pubkey: editionAccount,
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
},
|
||||
{
|
||||
pubkey: mintKey,
|
||||
isSigner: false,
|
||||
isWritable: true,
|
||||
},
|
||||
{
|
||||
pubkey: masterMintKey,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: updateAuthorityKey,
|
||||
isSigner: true,
|
||||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: mintAuthorityKey,
|
||||
isSigner: true,
|
||||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: metadataAccount,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: nameSymbolAccount,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: payer,
|
||||
isSigner: true,
|
||||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: programIds().token,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: SystemProgram.programId,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
{
|
||||
pubkey: SYSVAR_RENT_PUBKEY,
|
||||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
];
|
||||
instructions.push(
|
||||
new TransactionInstruction({
|
||||
keys,
|
||||
programId: metadataProgramId,
|
||||
data,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -392,7 +392,7 @@ export async function sendSignedTransaction({
|
|||
sentMessage?: string;
|
||||
successMessage?: string;
|
||||
timeout?: number;
|
||||
}): Promise<{ txid: string, slot: number }> {
|
||||
}): Promise<{ txid: string; slot: number }> {
|
||||
const rawTransaction = signedTransaction.serialize();
|
||||
const startTime = getUnixTs();
|
||||
let slot = 0;
|
||||
|
@ -415,7 +415,11 @@ export async function sendSignedTransaction({
|
|||
}
|
||||
})();
|
||||
try {
|
||||
slot = await awaitTransactionSignatureConfirmation(txid, timeout, connection);
|
||||
slot = await awaitTransactionSignatureConfirmation(
|
||||
txid,
|
||||
timeout,
|
||||
connection,
|
||||
);
|
||||
} catch (err) {
|
||||
if (err.timeout) {
|
||||
throw new Error('Timed out awaiting confirmation on transaction');
|
||||
|
@ -495,7 +499,7 @@ async function awaitTransactionSignatureConfirmation(
|
|||
try {
|
||||
subId = connection.onSignature(
|
||||
txid,
|
||||
(result) => {
|
||||
result => {
|
||||
console.log('WS confirmed', txid, result);
|
||||
done = true;
|
||||
if (result.err) {
|
||||
|
@ -544,11 +548,17 @@ async function awaitTransactionSignatureConfirmation(
|
|||
await sleep(500);
|
||||
}
|
||||
})();
|
||||
}).catch(_ => {
|
||||
connection.removeSignatureListener(subId);
|
||||
}).then(_ => {
|
||||
connection.removeSignatureListener(subId);
|
||||
});
|
||||
})
|
||||
.catch(_ => {
|
||||
//@ts-ignore
|
||||
if (connection._signatureSubscriptions[subId])
|
||||
connection.removeSignatureListener(subId);
|
||||
})
|
||||
.then(_ => {
|
||||
//@ts-ignore
|
||||
if (connection._signatureSubscriptions[subId])
|
||||
connection.removeSignatureListener(subId);
|
||||
});
|
||||
done = true;
|
||||
return slot;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import EventEmitter from "eventemitter3";
|
||||
import { PublicKey, Transaction } from "@solana/web3.js";
|
||||
import { notify } from "../../utils/notifications";
|
||||
import { WalletAdapter } from "@solana/wallet-base";
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { PublicKey, Transaction } from '@solana/web3.js';
|
||||
import { notify } from '../../utils/notifications';
|
||||
import { WalletAdapter } from '@solana/wallet-base';
|
||||
|
||||
type PhantomEvent = "disconnect" | "connect";
|
||||
type PhantomEvent = 'disconnect' | 'connect';
|
||||
type PhantomRequestMethod =
|
||||
| "connect"
|
||||
| "disconnect"
|
||||
| "signTransaction"
|
||||
| "signAllTransactions";
|
||||
| 'connect'
|
||||
| 'disconnect'
|
||||
| 'signTransaction'
|
||||
| 'signAllTransactions';
|
||||
|
||||
interface PhantomProvider {
|
||||
publicKey?: PublicKey;
|
||||
|
@ -26,6 +26,7 @@ export class PhantomWalletAdapter
|
|||
extends EventEmitter
|
||||
implements WalletAdapter {
|
||||
_provider: PhantomProvider | undefined;
|
||||
_cachedCorrectKey?: PublicKey;
|
||||
constructor() {
|
||||
super();
|
||||
this.connect = this.connect.bind(this);
|
||||
|
@ -40,7 +41,7 @@ export class PhantomWalletAdapter
|
|||
}
|
||||
|
||||
async signAllTransactions(
|
||||
transactions: Transaction[]
|
||||
transactions: Transaction[],
|
||||
): Promise<Transaction[]> {
|
||||
if (!this._provider) {
|
||||
return transactions;
|
||||
|
@ -50,7 +51,13 @@ export class PhantomWalletAdapter
|
|||
}
|
||||
|
||||
get publicKey() {
|
||||
return this._provider?.publicKey || null;
|
||||
// Due to weird phantom bug where their public key isnt quite like ours
|
||||
if (!this._cachedCorrectKey && this._provider?.publicKey)
|
||||
this._cachedCorrectKey = new PublicKey(
|
||||
this._provider.publicKey.toBase58(),
|
||||
);
|
||||
|
||||
return this._cachedCorrectKey || null;
|
||||
}
|
||||
|
||||
async signTransaction(transaction: Transaction) {
|
||||
|
@ -70,32 +77,32 @@ export class PhantomWalletAdapter
|
|||
if ((window as any)?.solana?.isPhantom) {
|
||||
provider = (window as any).solana;
|
||||
} else {
|
||||
window.open("https://phantom.app/", "_blank");
|
||||
window.open('https://phantom.app/', '_blank');
|
||||
notify({
|
||||
message: "Phantom Error",
|
||||
description: "Please install Phantom wallet from Chrome ",
|
||||
message: 'Phantom Error',
|
||||
description: 'Please install Phantom wallet from Chrome ',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
provider.on('connect', () => {
|
||||
this._provider = provider;
|
||||
this.emit("connect");
|
||||
})
|
||||
this.emit('connect');
|
||||
});
|
||||
|
||||
if (!provider.isConnected) {
|
||||
await provider.connect();
|
||||
}
|
||||
|
||||
this._provider = provider;
|
||||
this.emit("connect");
|
||||
}
|
||||
this.emit('connect');
|
||||
};
|
||||
|
||||
disconnect() {
|
||||
if (this._provider) {
|
||||
this._provider.disconnect();
|
||||
this._provider = undefined;
|
||||
this.emit("disconnect");
|
||||
this.emit('disconnect');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@ import {
|
|||
createAssociatedTokenAccountInstruction,
|
||||
createMint,
|
||||
createMetadata,
|
||||
transferMetadata,
|
||||
transferUpdateAuthority,
|
||||
programIds,
|
||||
sendTransaction,
|
||||
sendTransactions,
|
||||
notify,
|
||||
ENV,
|
||||
updateMetadata,
|
||||
createMasterEdition,
|
||||
} from '@oyster/common';
|
||||
import React from 'react';
|
||||
import { MintLayout, Token } from '@solana/spl-token';
|
||||
|
@ -61,18 +63,23 @@ export const mintNFT = async (
|
|||
MintLayout.span,
|
||||
);
|
||||
|
||||
// This owner is a temporary signer and owner of metadata we use to circumvent requesting signing
|
||||
// twice post Arweave. We store in an account (payer) and use it post-Arweave to update MD with new link
|
||||
// then give control back to the user.
|
||||
const owner = new Account();
|
||||
const payer = new Account();
|
||||
const instructions: TransactionInstruction[] = [...pushInstructions];
|
||||
const signers: Account[] = [...pushSigners, owner];
|
||||
|
||||
// This is only temporarily owned by wallet...transferred to program by createMasterEdition below
|
||||
const mintKey = createMint(
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
mintRent,
|
||||
0,
|
||||
owner.publicKey,
|
||||
owner.publicKey,
|
||||
// Some weird bug with phantom where it's public key doesnt mesh with data encode well
|
||||
wallet.publicKey,
|
||||
wallet.publicKey,
|
||||
signers,
|
||||
);
|
||||
|
||||
|
@ -100,23 +107,23 @@ export const mintNFT = async (
|
|||
TOKEN_PROGRAM_ID,
|
||||
mintKey,
|
||||
recipientKey,
|
||||
owner.publicKey,
|
||||
// Some weird bug with phantom where it's public key doesnt mesh with data encode well
|
||||
new PublicKey(wallet.publicKey.toBase58()),
|
||||
[],
|
||||
1,
|
||||
),
|
||||
);
|
||||
|
||||
const [metadataAccount, metadataOwnerAccount] = await createMetadata(
|
||||
const [metadataAccount, nameSymbolAccount] = await createMetadata(
|
||||
metadata.symbol,
|
||||
metadata.name,
|
||||
`https://-------.---/rfX69WKd7Bin_RTbcnH4wM3BuWWsR_ZhWSSqZBLYdMY`,
|
||||
false,
|
||||
true,
|
||||
payer.publicKey,
|
||||
mintKey,
|
||||
owner.publicKey,
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
signers,
|
||||
);
|
||||
|
||||
const block = await connection.getRecentBlockhash('singleGossip');
|
||||
|
@ -125,41 +132,74 @@ export const mintNFT = async (
|
|||
fromPubkey: wallet.publicKey,
|
||||
toPubkey: payer.publicKey,
|
||||
lamports: block.feeCalculator.lamportsPerSignature * 2,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await sendTransaction(
|
||||
let masterEdInstruction: TransactionInstruction[] = [];
|
||||
let masterEdSigner: Account[] = [payer];
|
||||
|
||||
// This mint, which allows limited editions to be made, stays with user's wallet.
|
||||
const masterMint = createMint(
|
||||
masterEdInstruction,
|
||||
wallet.publicKey,
|
||||
mintRent,
|
||||
0,
|
||||
// Some weird bug with phantom where it's public key doesnt mesh with data encode well
|
||||
wallet.publicKey,
|
||||
wallet.publicKey,
|
||||
masterEdSigner,
|
||||
);
|
||||
|
||||
// In this instruction, mint authority will be removed from the main mint, while
|
||||
// minting authority will be maintained for the master mint (which we want.)
|
||||
await createMasterEdition(
|
||||
metadata.symbol,
|
||||
metadata.name,
|
||||
undefined,
|
||||
mintKey,
|
||||
masterMint,
|
||||
payer.publicKey,
|
||||
wallet.publicKey,
|
||||
instructions,
|
||||
wallet.publicKey,
|
||||
);
|
||||
|
||||
const txIds: string[] = [];
|
||||
const response = await sendTransactions(
|
||||
connection,
|
||||
wallet,
|
||||
instructions,
|
||||
signers,
|
||||
[instructions, masterEdInstruction],
|
||||
[signers, masterEdSigner],
|
||||
true,
|
||||
true,
|
||||
'max',
|
||||
false,
|
||||
block);
|
||||
txId => {
|
||||
txIds.push(txId);
|
||||
},
|
||||
undefined,
|
||||
block,
|
||||
);
|
||||
|
||||
// this means we're done getting AR txn setup. Ship it off to ARWeave!
|
||||
const data = new FormData();
|
||||
|
||||
const tags = realFiles.reduce(
|
||||
(
|
||||
acc: Record<string, Array<{ name: string; value: string }>>,
|
||||
f,
|
||||
) => {
|
||||
(acc: Record<string, Array<{ name: string; value: string }>>, f) => {
|
||||
acc[f.name] = [{ name: 'mint', value: mintKey.toBase58() }];
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
data.append('tags', JSON.stringify(tags));
|
||||
data.append('transaction', response.txid);
|
||||
data.append('transaction', txIds[0]);
|
||||
realFiles.map(f => data.append('file[]', f));
|
||||
|
||||
const result: IArweaveResult = await (
|
||||
await fetch(
|
||||
// TODO: add CNAME
|
||||
env === 'mainnet-beta' ?
|
||||
'https://us-central1-principal-lane-200702.cloudfunctions.net/uploadFileProd' :
|
||||
'https://us-central1-principal-lane-200702.cloudfunctions.net/uploadFile',
|
||||
env === 'mainnet-beta'
|
||||
? 'https://us-central1-principal-lane-200702.cloudfunctions.net/uploadFileProd'
|
||||
: 'https://us-central1-principal-lane-200702.cloudfunctions.net/uploadFile',
|
||||
{
|
||||
method: 'POST',
|
||||
body: data,
|
||||
|
@ -170,8 +210,7 @@ export const mintNFT = async (
|
|||
const metadataFile = result.messages?.find(
|
||||
m => m.filename == RESERVED_TXN_MANIFEST,
|
||||
);
|
||||
if (metadataFile?.transactionId && wallet.publicKey)
|
||||
{
|
||||
if (metadataFile?.transactionId && wallet.publicKey) {
|
||||
const updateInstructions: TransactionInstruction[] = [];
|
||||
const updateSigners: Account[] = [payer];
|
||||
|
||||
|
@ -181,23 +220,19 @@ export const mintNFT = async (
|
|||
metadata.symbol,
|
||||
metadata.name,
|
||||
arweaveLink,
|
||||
undefined,
|
||||
mintKey,
|
||||
payer.publicKey,
|
||||
updateInstructions,
|
||||
updateSigners,
|
||||
metadataAccount,
|
||||
metadataOwnerAccount,
|
||||
nameSymbolAccount,
|
||||
);
|
||||
|
||||
await transferMetadata(
|
||||
metadata.symbol,
|
||||
metadata.name,
|
||||
await transferUpdateAuthority(
|
||||
metadataAccount,
|
||||
payer.publicKey,
|
||||
wallet.publicKey,
|
||||
updateInstructions,
|
||||
updateSigners,
|
||||
metadataAccount,
|
||||
metadataOwnerAccount,
|
||||
);
|
||||
|
||||
const txid = await sendTransaction(
|
||||
|
@ -207,12 +242,16 @@ export const mintNFT = async (
|
|||
updateSigners,
|
||||
true,
|
||||
'singleGossip',
|
||||
true
|
||||
true,
|
||||
);
|
||||
|
||||
notify({
|
||||
message: 'Art created on Solana',
|
||||
description: <a href={arweaveLink} target="_blank" >Arweave Link</a>,
|
||||
description: (
|
||||
<a href={arweaveLink} target="_blank">
|
||||
Arweave Link
|
||||
</a>
|
||||
),
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import './../styles.less';
|
|||
import { mintNFT } from '../../actions';
|
||||
import {
|
||||
MAX_METADATA_LEN,
|
||||
MAX_OWNER_LEN,
|
||||
MAX_NAME_SYMBOL_LEN,
|
||||
MAX_URI_LENGTH,
|
||||
useConnection,
|
||||
useWallet,
|
||||
|
@ -28,7 +28,11 @@ import {
|
|||
MetadataCategory,
|
||||
useConnectionConfig,
|
||||
} from '@oyster/common';
|
||||
import { getAssetCostToStore, LAMPORT_MULTIPLIER, solanaToUSD } from '../../utils/assets';
|
||||
import {
|
||||
getAssetCostToStore,
|
||||
LAMPORT_MULTIPLIER,
|
||||
solanaToUSD,
|
||||
} from '../../utils/assets';
|
||||
import { Connection } from '@solana/web3.js';
|
||||
import { MintLayout } from '@solana/spl-token';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
|
@ -41,8 +45,8 @@ export const ArtCreateView = () => {
|
|||
const connection = useConnection();
|
||||
const { env } = useConnectionConfig();
|
||||
const { wallet, connected } = useWallet();
|
||||
const { step_param }: { step_param: string } = useParams()
|
||||
const history = useHistory()
|
||||
const { step_param }: { step_param: string } = useParams();
|
||||
const history = useHistory();
|
||||
|
||||
const [step, setStep] = useState<number>(0);
|
||||
const [saving, setSaving] = useState<boolean>(false);
|
||||
|
@ -59,45 +63,48 @@ export const ArtCreateView = () => {
|
|||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (step_param) setStep(parseInt(step_param))
|
||||
else gotoStep(0)
|
||||
}, [step_param])
|
||||
if (step_param) setStep(parseInt(step_param));
|
||||
else gotoStep(0);
|
||||
}, [step_param]);
|
||||
|
||||
const gotoStep = (_step: number) => {
|
||||
history.push(`/art/create/${_step.toString()}`)
|
||||
}
|
||||
history.push(`/art/create/${_step.toString()}`);
|
||||
};
|
||||
|
||||
// store files
|
||||
const mint = async () => {
|
||||
const metadata = {
|
||||
...(attributes as any),
|
||||
image: attributes.files && attributes.files?.[0] && attributes.files[0].name,
|
||||
image:
|
||||
attributes.files && attributes.files?.[0] && attributes.files[0].name,
|
||||
files: (attributes?.files || []).map(f => f.name),
|
||||
}
|
||||
setSaving(true)
|
||||
const inte = setInterval(() => setProgress(prog => prog + 1), 600)
|
||||
};
|
||||
setSaving(true);
|
||||
const inte = setInterval(() => setProgress(prog => prog + 1), 600);
|
||||
// Update progress inside mintNFT
|
||||
await mintNFT(connection, wallet, env, (attributes?.files || []), metadata)
|
||||
clearInterval(inte)
|
||||
}
|
||||
await mintNFT(connection, wallet, env, attributes?.files || [], metadata);
|
||||
clearInterval(inte);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row style={{ paddingTop: 50 }}>
|
||||
{!saving && <Col xl={5}>
|
||||
<Steps
|
||||
progressDot
|
||||
direction="vertical"
|
||||
current={step}
|
||||
style={{ width: 200, marginLeft: 20, marginRight: 30 }}
|
||||
>
|
||||
<Step title="Category" />
|
||||
<Step title="Upload" />
|
||||
<Step title="Info" />
|
||||
<Step title="Royalties" />
|
||||
<Step title="Launch" />
|
||||
</Steps>
|
||||
</Col>}
|
||||
{!saving && (
|
||||
<Col xl={5}>
|
||||
<Steps
|
||||
progressDot
|
||||
direction="vertical"
|
||||
current={step}
|
||||
style={{ width: 200, marginLeft: 20, marginRight: 30 }}
|
||||
>
|
||||
<Step title="Category" />
|
||||
<Step title="Upload" />
|
||||
<Step title="Info" />
|
||||
<Step title="Royalties" />
|
||||
<Step title="Launch" />
|
||||
</Steps>
|
||||
</Col>
|
||||
)}
|
||||
<Col {...(saving ? { xl: 24 } : { xl: 16 })}>
|
||||
{step === 0 && (
|
||||
<CategoryStep
|
||||
|
@ -146,17 +153,19 @@ export const ArtCreateView = () => {
|
|||
confirm={() => gotoStep(6)}
|
||||
/>
|
||||
)}
|
||||
{step === 6 && (
|
||||
<Congrats />
|
||||
{step === 6 && <Congrats />}
|
||||
{0 < step && step < 5 && (
|
||||
<Button onClick={() => gotoStep(step - 1)}>Back</Button>
|
||||
)}
|
||||
{(0 < step && step < 5) && <Button onClick={() => gotoStep(step - 1)}>Back</Button>}
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const CategoryStep = (props: { confirm: (category: MetadataCategory) => void }) => {
|
||||
const CategoryStep = (props: {
|
||||
confirm: (category: MetadataCategory) => void;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<Row className="call-to-action">
|
||||
|
@ -190,7 +199,6 @@ const CategoryStep = (props: { confirm: (category: MetadataCategory) => void })
|
|||
<div className="type-btn-description">MP3, WAV, FLAC</div>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
</Row>
|
||||
<Row>
|
||||
<Button
|
||||
|
@ -228,29 +236,29 @@ const UploadStep = (props: {
|
|||
setAttributes: (attr: IMetadataExtension) => void;
|
||||
confirm: () => void;
|
||||
}) => {
|
||||
const [mainFile, setMainFile] = useState<any>()
|
||||
const [coverFile, setCoverFile] = useState<any>()
|
||||
const [image, setImage] = useState<string>("")
|
||||
const [mainFile, setMainFile] = useState<any>();
|
||||
const [coverFile, setCoverFile] = useState<any>();
|
||||
const [image, setImage] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
props.setAttributes({
|
||||
...props.attributes,
|
||||
files: []
|
||||
})
|
||||
}, [])
|
||||
files: [],
|
||||
});
|
||||
}, []);
|
||||
|
||||
const uploadMsg = (category: MetadataCategory) => {
|
||||
switch (category) {
|
||||
case MetadataCategory.Audio:
|
||||
return "Upload your audio creation (MP3, FLAC, WAV)"
|
||||
return 'Upload your audio creation (MP3, FLAC, WAV)';
|
||||
case MetadataCategory.Image:
|
||||
return "Upload your image creation (PNG, JPG, GIF)"
|
||||
return 'Upload your image creation (PNG, JPG, GIF)';
|
||||
case MetadataCategory.Video:
|
||||
return "Upload your video creation (MP4)"
|
||||
return 'Upload your video creation (MP4)';
|
||||
default:
|
||||
return "Please go back and choose a category"
|
||||
return 'Please go back and choose a category';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -271,61 +279,62 @@ const UploadStep = (props: {
|
|||
multiple={false}
|
||||
customRequest={info => {
|
||||
// dont upload files here, handled outside of the control
|
||||
info?.onSuccess?.({}, null as any)
|
||||
info?.onSuccess?.({}, null as any);
|
||||
}}
|
||||
fileList={mainFile ? [mainFile] : []}
|
||||
onChange={async info => {
|
||||
const file = info.file.originFileObj;
|
||||
if (file) setMainFile(file)
|
||||
if (file) setMainFile(file);
|
||||
if (props.attributes.category != MetadataCategory.Audio) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
setImage((event.target?.result as string) || '')
|
||||
}
|
||||
if (file) reader.readAsDataURL(file)
|
||||
setImage((event.target?.result as string) || '');
|
||||
};
|
||||
if (file) reader.readAsDataURL(file);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="ant-upload-drag-icon">
|
||||
<h3 style={{ fontWeight: 700 }}>Upload your creation</h3>
|
||||
</div>
|
||||
<p className="ant-upload-text">
|
||||
Drag and drop, or click to browse
|
||||
</p>
|
||||
<p className="ant-upload-text">Drag and drop, or click to browse</p>
|
||||
</Dragger>
|
||||
</Row>
|
||||
{props.attributes.category == MetadataCategory.Audio &&
|
||||
{props.attributes.category == MetadataCategory.Audio && (
|
||||
<Row className="content-action">
|
||||
<h3>Optionally, you can upload a cover image or video (PNG, JPG, GIF, MP4)</h3>
|
||||
<h3>
|
||||
Optionally, you can upload a cover image or video (PNG, JPG, GIF,
|
||||
MP4)
|
||||
</h3>
|
||||
<Dragger
|
||||
style={{ padding: 20 }}
|
||||
multiple={false}
|
||||
customRequest={info => {
|
||||
// dont upload files here, handled outside of the control
|
||||
info?.onSuccess?.({}, null as any)
|
||||
info?.onSuccess?.({}, null as any);
|
||||
}}
|
||||
fileList={coverFile ? [coverFile] : []}
|
||||
onChange={async info => {
|
||||
const file = info.file.originFileObj;
|
||||
if (file) setCoverFile(file)
|
||||
if (file) setCoverFile(file);
|
||||
if (props.attributes.category == MetadataCategory.Audio) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
setImage((event.target?.result as string) || '')
|
||||
}
|
||||
if (file) reader.readAsDataURL(file)
|
||||
setImage((event.target?.result as string) || '');
|
||||
};
|
||||
if (file) reader.readAsDataURL(file);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="ant-upload-drag-icon">
|
||||
<h3 style={{ fontWeight: 700 }}>Upload your cover image or video</h3>
|
||||
<h3 style={{ fontWeight: 700 }}>
|
||||
Upload your cover image or video
|
||||
</h3>
|
||||
</div>
|
||||
<p className="ant-upload-text">
|
||||
Drag and drop, or click to browse
|
||||
</p>
|
||||
<p className="ant-upload-text">Drag and drop, or click to browse</p>
|
||||
</Dragger>
|
||||
</Row>
|
||||
}
|
||||
)}
|
||||
<Row>
|
||||
<Button
|
||||
type="primary"
|
||||
|
@ -335,8 +344,8 @@ const UploadStep = (props: {
|
|||
...props.attributes,
|
||||
files: [mainFile, coverFile].filter(f => f),
|
||||
image,
|
||||
})
|
||||
props.confirm()
|
||||
});
|
||||
props.confirm();
|
||||
}}
|
||||
className="action-btn"
|
||||
>
|
||||
|
@ -348,8 +357,8 @@ const UploadStep = (props: {
|
|||
};
|
||||
|
||||
interface Royalty {
|
||||
creator_key: string,
|
||||
amount: number
|
||||
creator_key: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
const InfoStep = (props: {
|
||||
|
@ -357,15 +366,17 @@ const InfoStep = (props: {
|
|||
setAttributes: (attr: IMetadataExtension) => void;
|
||||
confirm: () => void;
|
||||
}) => {
|
||||
const [creators, setCreators] = useState<Array<UserValue>>([])
|
||||
const [royalties, setRoyalties] = useState<Array<Royalty>>([])
|
||||
const [creators, setCreators] = useState<Array<UserValue>>([]);
|
||||
const [royalties, setRoyalties] = useState<Array<Royalty>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setRoyalties(creators.map(creator => ({
|
||||
creator_key: creator.key,
|
||||
amount: Math.trunc(100 / creators.length),
|
||||
})))
|
||||
}, [creators])
|
||||
setRoyalties(
|
||||
creators.map(creator => ({
|
||||
creator_key: creator.key,
|
||||
amount: Math.trunc(100 / creators.length),
|
||||
})),
|
||||
);
|
||||
}, [creators]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -422,9 +433,7 @@ const InfoStep = (props: {
|
|||
</label>
|
||||
<label className="action-field">
|
||||
<span className="field-title">Creators</span>
|
||||
<UserSearch
|
||||
setCreators={setCreators}
|
||||
/>
|
||||
<UserSearch setCreators={setCreators} />
|
||||
</label>
|
||||
<label className="action-field">
|
||||
<span className="field-title">Description</span>
|
||||
|
@ -446,7 +455,11 @@ const InfoStep = (props: {
|
|||
<Row>
|
||||
<label className="action-field" style={{ width: '100%' }}>
|
||||
<span className="field-title">Royalties Split</span>
|
||||
<RoyaltiesSplitter creators={creators} royalties={royalties} setRoyalties={setRoyalties} />
|
||||
<RoyaltiesSplitter
|
||||
creators={creators}
|
||||
royalties={royalties}
|
||||
setRoyalties={setRoyalties}
|
||||
/>
|
||||
</label>
|
||||
</Row>
|
||||
<Row>
|
||||
|
@ -465,63 +478,78 @@ const InfoStep = (props: {
|
|||
|
||||
const shuffle = (array: Array<any>) => {
|
||||
array.sort(() => Math.random() - 0.5);
|
||||
}
|
||||
};
|
||||
|
||||
const RoyaltiesSplitter = (props: {
|
||||
creators: Array<UserValue>,
|
||||
royalties: Array<Royalty>,
|
||||
setRoyalties: Function,
|
||||
creators: Array<UserValue>;
|
||||
royalties: Array<Royalty>;
|
||||
setRoyalties: Function;
|
||||
}) => {
|
||||
return (
|
||||
<Col>
|
||||
{props.creators.map((creator, idx) => {
|
||||
const royalty = props.royalties.find(royalty => royalty.creator_key == creator.key)
|
||||
if (!royalty) return null
|
||||
const royalty = props.royalties.find(
|
||||
royalty => royalty.creator_key == creator.key,
|
||||
);
|
||||
if (!royalty) return null;
|
||||
|
||||
const amt = royalty.amount
|
||||
const amt = royalty.amount;
|
||||
const handleSlide = (newAmt: number) => {
|
||||
const othersRoyalties = props.royalties.filter(_royalty => _royalty.creator_key != royalty.creator_key)
|
||||
if (othersRoyalties.length < 1) return
|
||||
shuffle(othersRoyalties)
|
||||
const others_n = props.royalties.length - 1
|
||||
const sign = Math.sign(newAmt - amt)
|
||||
let remaining = Math.abs(newAmt - amt)
|
||||
let count = 0
|
||||
const othersRoyalties = props.royalties.filter(
|
||||
_royalty => _royalty.creator_key != royalty.creator_key,
|
||||
);
|
||||
if (othersRoyalties.length < 1) return;
|
||||
shuffle(othersRoyalties);
|
||||
const others_n = props.royalties.length - 1;
|
||||
const sign = Math.sign(newAmt - amt);
|
||||
let remaining = Math.abs(newAmt - amt);
|
||||
let count = 0;
|
||||
while (remaining > 0 && count < 100) {
|
||||
const idx = count % others_n
|
||||
const _royalty = othersRoyalties[idx]
|
||||
const idx = count % others_n;
|
||||
const _royalty = othersRoyalties[idx];
|
||||
if (
|
||||
(0 < _royalty.amount && _royalty.amount < 100) // Normal
|
||||
|| (_royalty.amount == 0 && sign < 0) // Low limit
|
||||
|| (_royalty.amount == 100 && sign > 0) // High limit
|
||||
(0 < _royalty.amount && _royalty.amount < 100) || // Normal
|
||||
(_royalty.amount == 0 && sign < 0) || // Low limit
|
||||
(_royalty.amount == 100 && sign > 0) // High limit
|
||||
) {
|
||||
_royalty.amount -= sign
|
||||
remaining -= 1
|
||||
_royalty.amount -= sign;
|
||||
remaining -= 1;
|
||||
}
|
||||
count += 1
|
||||
count += 1;
|
||||
}
|
||||
|
||||
props.setRoyalties(props.royalties.map(_royalty => {
|
||||
const computed_amount = othersRoyalties.find(newRoyalty =>
|
||||
newRoyalty.creator_key == _royalty.creator_key
|
||||
)?.amount
|
||||
return {
|
||||
..._royalty,
|
||||
amount: _royalty.creator_key == royalty.creator_key ? newAmt : computed_amount,
|
||||
}
|
||||
}))
|
||||
}
|
||||
props.setRoyalties(
|
||||
props.royalties.map(_royalty => {
|
||||
const computed_amount = othersRoyalties.find(
|
||||
newRoyalty => newRoyalty.creator_key == _royalty.creator_key,
|
||||
)?.amount;
|
||||
return {
|
||||
..._royalty,
|
||||
amount:
|
||||
_royalty.creator_key == royalty.creator_key
|
||||
? newAmt
|
||||
: computed_amount,
|
||||
};
|
||||
}),
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Row key={idx} style={{ margin: '5px auto' }}>
|
||||
<Col span={11} className="slider-elem">{creator.label}</Col>
|
||||
<Col span={8} className="slider-elem">{amt}%</Col>
|
||||
<Col span={4}><Slider value={amt} onChange={handleSlide} /></Col>
|
||||
<Col span={11} className="slider-elem">
|
||||
{creator.label}
|
||||
</Col>
|
||||
<Col span={8} className="slider-elem">
|
||||
{amt}%
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Slider value={amt} onChange={handleSlide} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const RoyaltiesStep = (props: {
|
||||
attributes: IMetadataExtension;
|
||||
|
@ -595,15 +623,9 @@ const LaunchStep = (props: {
|
|||
const [USDcost, setUSDcost] = useState(0);
|
||||
useEffect(() => {
|
||||
const rentCall = Promise.all([
|
||||
props.connection.getMinimumBalanceForRentExemption(
|
||||
MintLayout.span,
|
||||
),
|
||||
props.connection.getMinimumBalanceForRentExemption(
|
||||
MAX_METADATA_LEN,
|
||||
),
|
||||
props.connection.getMinimumBalanceForRentExemption(
|
||||
MAX_OWNER_LEN,
|
||||
)
|
||||
props.connection.getMinimumBalanceForRentExemption(MintLayout.span),
|
||||
props.connection.getMinimumBalanceForRentExemption(MAX_METADATA_LEN),
|
||||
props.connection.getMinimumBalanceForRentExemption(MAX_NAME_SYMBOL_LEN),
|
||||
]);
|
||||
|
||||
getAssetCostToStore([
|
||||
|
@ -630,8 +652,8 @@ const LaunchStep = (props: {
|
|||
}, [...files, setCost]);
|
||||
|
||||
useEffect(() => {
|
||||
cost && solanaToUSD(cost).then(setUSDcost)
|
||||
}, [cost])
|
||||
cost && solanaToUSD(cost).then(setUSDcost);
|
||||
}, [cost]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -661,21 +683,25 @@ const LaunchStep = (props: {
|
|||
value={props.attributes.royalty}
|
||||
suffix="%"
|
||||
/>
|
||||
{cost ? <div style={{ display: 'flex' }}>
|
||||
<Statistic
|
||||
className="create-statistic"
|
||||
title="Cost to Create"
|
||||
value={cost.toPrecision(3)}
|
||||
prefix="◎"
|
||||
/>
|
||||
<div style={{
|
||||
margin: "auto 0",
|
||||
color: "rgba(255, 255, 255, 0.4)",
|
||||
fontSize: "1.5rem",
|
||||
}}>
|
||||
${USDcost.toPrecision(2)}
|
||||
{cost ? (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<Statistic
|
||||
className="create-statistic"
|
||||
title="Cost to Create"
|
||||
value={cost.toPrecision(3)}
|
||||
prefix="◎"
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
margin: 'auto 0',
|
||||
color: 'rgba(255, 255, 255, 0.4)',
|
||||
fontSize: '1.5rem',
|
||||
}}
|
||||
>
|
||||
${USDcost.toPrecision(2)}
|
||||
</div>
|
||||
</div>
|
||||
</div> : (
|
||||
) : (
|
||||
<Spin />
|
||||
)}
|
||||
</Col>
|
||||
|
@ -703,46 +729,52 @@ const LaunchStep = (props: {
|
|||
};
|
||||
|
||||
const WaitingStep = (props: {
|
||||
mint: Function,
|
||||
progress: number,
|
||||
confirm: Function,
|
||||
mint: Function;
|
||||
progress: number;
|
||||
confirm: Function;
|
||||
}) => {
|
||||
|
||||
useEffect(() => {
|
||||
const func = async () => {
|
||||
await props.mint()
|
||||
props.confirm()
|
||||
}
|
||||
func()
|
||||
}, [])
|
||||
await props.mint();
|
||||
props.confirm();
|
||||
};
|
||||
func();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: 70 }}>
|
||||
<Progress
|
||||
type="circle"
|
||||
percent={props.progress}
|
||||
/>
|
||||
<Progress type="circle" percent={props.progress} />
|
||||
<div className="waiting-title">
|
||||
Your creation is being uploaded to the decentralized web...
|
||||
</div>
|
||||
<div className="waiting-subtitle">This can take up to 1 minute.</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const Congrats = () => {
|
||||
return <>
|
||||
<div style={{ marginTop: 70 }}>
|
||||
<div className="waiting-title">
|
||||
Congratulations, you created an NFT!
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginTop: 70 }}>
|
||||
<div className="waiting-title">
|
||||
Congratulations, you created an NFT!
|
||||
</div>
|
||||
<div className="congrats-button-container">
|
||||
<Button className="congrats-button">
|
||||
<span>Share it on Twitter</span>
|
||||
<span>></span>
|
||||
</Button>
|
||||
<Button className="congrats-button">
|
||||
<span>See it in your collection</span>
|
||||
<span>></span>
|
||||
</Button>
|
||||
<Button className="congrats-button">
|
||||
<span>Sell it via auction</span>
|
||||
<span>></span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="congrats-button-container">
|
||||
<Button className="congrats-button"><span>Share it on Twitter</span><span>></span></Button>
|
||||
<Button className="congrats-button"><span>See it in your collection</span><span>></span></Button>
|
||||
<Button className="congrats-button"><span>Sell it via auction</span><span>></span></Button>
|
||||
</div>
|
||||
</div>
|
||||
<Confetti />
|
||||
</>
|
||||
}
|
||||
|
||||
<Confetti />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue