Merge branch 'main' into lending/xcl-master

This commit is contained in:
jordansexton 2021-06-10 18:51:19 -05:00
commit 002723fa44
342 changed files with 20886 additions and 10561 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
REACT_APP_CLIENT_ID=BNxdRWx08cSTPlzMAaShlM62d4f8Tp6racfnCg_gaH0XQ1NfSGo3h5B_IkLtgSnPMhlxsSvhqugWm0x8x-VkUXA

View File

@ -8,6 +8,6 @@
"typescript.enablePromptUseWorkspaceTsdk": true,
"prettier.prettierPath": ".vscode/pnpify/prettier/index.js",
"cSpell.words": [
"Timelock"
]
}

19
assets/wallets/torus.svg Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="226.000000pt" height="248.000000pt" viewBox="0 0 226.000000 248.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.16, written by Peter Selinger 2001-2019
</metadata>
<g transform="translate(0.000000,248.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M482 2143 c-43 -21 -79 -64 -92 -111 -13 -48 -13 -246 0 -294 29
-103 92 -128 322 -128 l158 0 0 -588 0 -589 23 -43 c16 -30 37 -51 67 -67 39
-21 56 -23 182 -23 94 0 149 4 171 14 43 17 95 78 102 118 3 18 5 389 3 825
l-3 791 -31 39 c-59 74 -52 73 -484 73 -334 0 -389 -3 -418 -17z"/>
<path d="M1743 2144 c-57 -21 -126 -84 -154 -141 -35 -73 -32 -171 6 -241 54
-100 132 -146 245 -146 115 0 197 50 246 151 108 220 -110 459 -343 377z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 957 B

View File

@ -0,0 +1,16 @@
# This file specifies files that are *not* uploaded to Google Cloud Platform
# using gcloud. It follows the same syntax as .gitignore, with the addition of
# "#!include" directives (which insert the entries of the given .gitignore-style
# file at that point).
#
# For more information, run:
# $ gcloud topic gcloudignore
#
.gcloudignore
# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore
node_modules

View File

@ -0,0 +1,4 @@
gcloud functions deploy uploadFile --runtime nodejs12 --trigger-http --allow-unauthenticated
To deploy to prod, change function name to uploadFileProd in index.js, and CLUSTER url to mainnet, then
run above with uploadFileProd

View File

@ -0,0 +1,348 @@
// [START functions_http_form_data]
/**
* Parses a 'multipart/form-data' upload request
*
* @param {Object} req Cloud Function request context.
* @param {Object} res Cloud Function response context.
*/
const path = require('path');
const Arweave = require('arweave');
const { Storage } = require('@google-cloud/storage');
const os = require('os');
const fs = require('fs');
const crypto = require('crypto');
const { Account, Connection } = require('@solana/web3.js');
const mimeType = require('mime-types');
const fetch = require('node-fetch');
const storage = new Storage();
const BUCKET_NAME = 'us.artifacts.principal-lane-200702.appspot.com';
const FOLDER_NAME = 'arweave';
const ARWEAVE_KEYNAME = 'arweave.json';
const SOLANA_KEYNAME = 'arweave-sol-container.json';
const CLUSTER = 'https://devnet.solana.com';
//const CLUSTER = 'https://api.mainnet-beta.solana.com';
const SYSTEM = '11111111111111111111111111111111';
const MEMO = 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr';
const KEYHOLDER = {};
const FAIL = 'fail';
const SUCCESS = 'success';
const LAMPORT_MULTIPLIER = 10 ** 9;
const WINSTON_MULTIPLIER = 10 ** 12;
const RESERVED_TXN_MANIFEST = 'manifest.json';
function generateManifest(pathMap, indexPath) {
const manifest = {
manifest: 'arweave/paths',
version: '0.1.0',
paths: pathMap,
};
if (indexPath) {
if (!Object.keys(pathMap).includes(indexPath)) {
throw new Error(
`--index path not found in directory paths: ${indexPath}`,
);
}
manifest.index = {
path: indexPath,
};
}
return manifest;
}
const getKey = async function (name) {
if (KEYHOLDER[name]) return KEYHOLDER[name];
const options = {
destination: os.tmpdir() + '/' + name,
};
// Downloads the file
await storage
.bucket(BUCKET_NAME)
.file(FOLDER_NAME + '/' + name)
.download(options);
console.log(`Key downloaded to ${os.tmpdir()}/${name}`);
let rawdata = fs.readFileSync(os.tmpdir() + '/' + name);
let key;
try {
key = JSON.parse(rawdata);
} catch (e) {
key = rawdata.toString();
}
KEYHOLDER[name] = key;
return KEYHOLDER[name];
};
// Node.js doesn't have a built-in multipart/form-data parsing library.
// Instead, we can use the 'busboy' library from NPM to parse these requests.
const Busboy = require('busboy');
const arweaveConnection = Arweave.init({
host: 'arweave.net', // Hostname or IP address for a Arweave host
port: 443, // Port
protocol: 'https', // Network protocol http or https
timeout: 20000, // Network request timeouts in milliseconds
logging: true, // Enable network request logging
});
// FYI no streaming uploads as yet
// https://gist.github.com/CDDelta/e2af7e02314b2e0c3b5f9eb616c645a6
// Need to read entire thing into memory - Limits us to 2GB files. TODO come back and implemnet.
exports.uploadFile = async (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
if (req.method === 'OPTIONS') {
// Send response to OPTIONS requests
res.set('Access-Control-Allow-Methods', 'POST');
res.set('Access-Control-Allow-Headers', 'Content-Type');
res.set('Access-Control-Max-Age', '3600');
res.status(204).send('');
return;
}
if (req.method !== 'POST') {
// Return a "method not allowed" error
return res.status(405).end();
}
const solanaKey = await getKey(SOLANA_KEYNAME);
const solanaConnection = new Connection(CLUSTER, 'recent');
const solanaWallet = new Account(solanaKey);
const arweaveWallet = await getKey(ARWEAVE_KEYNAME);
console.log('Connections established.');
const busboy = new Busboy({ headers: req.headers });
const tmpdir = os.tmpdir();
const fieldPromises = [];
// This code will process each non-file field in the form.
busboy.on('field', (fieldname, val) => {
console.log('I see ' + fieldname);
fieldPromises.push(
new Promise(async (res, _) => {
if (fieldname === 'transaction') {
try {
console.log('Calling out for txn', val);
const transaction = await solanaConnection.getParsedConfirmedTransaction(
val,
);
console.log('I got the transaction');
// We expect the first command to be a SOL send from them to our holding account.
// Then after that it's memos of sha256 hashes of file contents.
const expectedSend =
transaction.transaction.message.instructions[0];
const isSystem = expectedSend.programId.toBase58() === SYSTEM;
const isToUs =
expectedSend.parsed.info.destination ===
solanaWallet.publicKey.toBase58();
console.log(
'Expected to send is',
JSON.stringify(expectedSend.parsed),
);
if (isSystem && isToUs) {
const amount = expectedSend.parsed.info.lamports;
const remainingMemos = transaction.transaction.message.instructions.filter(
i => i.programId.toBase58() === MEMO,
);
const memoMessages = remainingMemos.map(m => m.parsed);
res({
name: fieldname,
amount,
memoMessages,
});
} else
throw new Error(
'No payment found because either the program wasnt the system program or it wasnt to the holding account',
);
} catch (e) {
console.log(fieldname, e);
console.log('Setting txn anyway');
res({
name: fieldname,
amount: 0,
memoMessages: [],
});
}
} else if (fieldname === 'tags') {
try {
res({
name: fieldname,
...JSON.parse(val),
});
} catch (e) {
console.log(fieldname, e);
res({
name: fieldname,
});
}
}
}),
);
});
const fileWrites = [];
// This code will process each file uploaded.
busboy.on('file', (fieldname, file, filename) => {
// Note: os.tmpdir() points to an in-memory file system on GCF
// Thus, any files in it must fit in the instance's memory.
console.log(`Processed file ${filename}`);
const filepath = path.join(tmpdir, filename);
const writeStream = fs.createWriteStream(filepath);
file.pipe(writeStream);
// File was processed by Busboy; wait for it to be written.
// Note: GCF may not persist saved files across invocations.
// Persistent files must be kept in other locations
// (such as Cloud Storage buckets).
const promise = new Promise((resolve, reject) => {
file.on('end', () => {
writeStream.end();
});
writeStream.on('finish', resolve({ status: SUCCESS, filepath }));
writeStream.on(
'error',
reject({ status: FAIL, filepath, error: 'failed to save' }),
);
});
fileWrites.push(promise);
});
// Triggered once all uploaded files are processed by Busboy.
// We still need to wait for the disk writes (saves) to complete.
const body = { messages: [] };
busboy.on('finish', async () => {
console.log('Finish');
const filepaths = [
...(await Promise.all(fileWrites)),
{ filepath: RESERVED_TXN_MANIFEST, status: SUCCESS },
];
const fields = await Promise.all(fieldPromises);
const anchor = (await arweaveConnection.api.get('tx_anchor')).data;
console.log('The one guy is ' + fields.map(f => f.name).join(','));
const txn = fields.find(f => f.name === 'transaction');
const fieldTags = fields.find(f => f.name === 'tags');
if (!txn || !txn.amount) {
body.error = 'No transaction found with payment';
res.end(JSON.stringify(body));
return;
}
let runningTotal = txn.amount;
const conversionRates = JSON.parse(
await (
await fetch(
'https://api.coingecko.com/api/v3/simple/price?ids=solana,arweave&vs_currencies=usd',
)
).text(),
);
// To figure out how much solana is required, multiply ar byte cost by this number
const arMultiplier =
conversionRates.arweave.usd / conversionRates.solana.usd;
const paths = {};
for (let i = 0; i < filepaths.length; i++) {
const f = filepaths[i];
if (f.status == FAIL) {
body.messages.push(f);
} else {
const { filepath } = f;
const parts = filepath.split('/');
const filename = parts[parts.length - 1];
try {
let data, fileSizeInBytes, mime;
if (filepath == RESERVED_TXN_MANIFEST) {
const manifest = await generateManifest(paths, 'metadata.json');
data = Buffer.from(JSON.stringify(manifest), 'utf8');
fileSizeInBytes = data.byteLength;
mime = 'application/x.arweave-manifest+json';
} else {
data = fs.readFileSync(filepath);
// Have to get separate Buffer since buffers are stateful
const hashSum = crypto.createHash('sha256');
hashSum.update(data.toString());
const hex = hashSum.digest('hex');
if (!txn.memoMessages.find(m => m === hex)) {
body.messages.push({
filename,
status: FAIL,
error: `Unable to find proof that you paid for this file, your hash is ${hex}, comparing to ${txn.memoMessages.join(
',',
)}`,
});
continue;
}
const stats = fs.statSync(filepath);
fileSizeInBytes = stats.size;
mime = mimeType.lookup(filepath);
}
const costSizeInWinstons = parseInt(
await (
await fetch(
'https://arweave.net/price/' + fileSizeInBytes.toString(),
)
).text(),
);
const costToStoreInSolana =
(costSizeInWinstons * arMultiplier) / WINSTON_MULTIPLIER;
runningTotal -= costToStoreInSolana * LAMPORT_MULTIPLIER;
if (runningTotal > 0) {
const transaction = await arweaveConnection.createTransaction(
{ data: data, last_tx: anchor },
arweaveWallet,
);
transaction.addTag('Content-Type', mime);
if (fieldTags) {
const tags =
fieldTags[filepath.split('/')[filepath.split('/').length - 1]];
if (tags) tags.map(t => transaction.addTag(t.name, t.value));
}
await arweaveConnection.transactions.sign(
transaction,
arweaveWallet,
);
await arweaveConnection.transactions.post(transaction);
body.messages.push({
filename,
status: SUCCESS,
transactionId: transaction.id,
});
paths[filename] = { id: transaction.id };
} else {
body.messages.push({
filename,
status: FAIL,
error: `Not enough funds provided to push this file, you need at least ${costToStoreInSolana} SOL or ${costSize} AR`,
});
}
} catch (e) {
console.log(e);
body.messages.push({ filename, status: FAIL, error: e.toString() });
}
}
}
res.end(JSON.stringify(body));
});
busboy.end(req.rawBody);
};
// [END functions_http_form_data]

View File

@ -0,0 +1,32 @@
{
"name": "arweave-push",
"version": "0.0.1",
"private": true,
"license": "Apache-2.0",
"author": "Solana Labs",
"repository": {
"type": "git",
"url": "https://github.com/solana-labs/oyster.git"
},
"engines": {
"node": ">=12.0.0"
},
"scripts": {
"test": "mocha test/*.test.js --timeout=60000"
},
"devDependencies": {
"mocha": "^8.0.0",
"proxyquire": "^2.1.0",
"sinon": "^10.0.0"
},
"dependencies": {
"@google-cloud/storage": "^5.0.0",
"busboy": "^0.3.0",
"escape-html": "^1.0.3",
"arweave": "1.10.13",
"@solana/web3.js": "^1.5.0",
"mime-types": "2.1.30",
"node-fetch": "2.6.1",
"coingecko-api": "1.0.10"
}
}

View File

@ -12,9 +12,7 @@
"ast": {
"absolutePath": "@openzeppelin/contracts/utils/Context.sol",
"exportedSymbols": {
"Context": [
3618
]
"Context": [3618]
},
"id": 3619,
"license": "MIT",
@ -22,15 +20,7 @@
"nodes": [
{
"id": 3597,
"literals": [
"solidity",
">=",
"0.6",
".0",
"<",
"0.8",
".0"
],
"literals": ["solidity", ">=", "0.6", ".0", "<", "0.8", ".0"],
"nodeType": "PragmaDirective",
"src": "33:31:9"
},
@ -42,9 +32,7 @@
"documentation": null,
"fullyImplemented": true,
"id": 3618,
"linearizedBaseContracts": [
3618
],
"linearizedBaseContracts": [3618],
"name": "Context",
"nodeType": "ContractDefinition",
"nodes": [
@ -274,24 +262,14 @@
"attributes": {
"absolutePath": "@openzeppelin/contracts/utils/Context.sol",
"exportedSymbols": {
"Context": [
3618
]
"Context": [3618]
},
"license": "MIT"
},
"children": [
{
"attributes": {
"literals": [
"solidity",
">=",
"0.6",
".0",
"<",
"0.8",
".0"
]
"literals": ["solidity", ">=", "0.6", ".0", "<", "0.8", ".0"]
},
"id": 3597,
"name": "PragmaDirective",
@ -300,18 +278,12 @@
{
"attributes": {
"abstract": true,
"baseContracts": [
null
],
"contractDependencies": [
null
],
"baseContracts": [null],
"contractDependencies": [null],
"contractKind": "contract",
"documentation": null,
"fullyImplemented": true,
"linearizedBaseContracts": [
3618
],
"linearizedBaseContracts": [3618],
"name": "Context",
"scope": 3619
},
@ -322,9 +294,7 @@
"implemented": true,
"isConstructor": false,
"kind": "function",
"modifiers": [
null
],
"modifiers": [null],
"name": "_msgSender",
"overrides": null,
"scope": 3618,
@ -335,9 +305,7 @@
"children": [
{
"attributes": {
"parameters": [
null
]
"parameters": [null]
},
"children": [],
"id": 3598,
@ -402,9 +370,7 @@
{
"attributes": {
"argumentTypes": null,
"overloadedDeclarations": [
null
],
"overloadedDeclarations": [null],
"referencedDeclaration": -15,
"type": "msg",
"value": "msg"
@ -439,9 +405,7 @@
"implemented": true,
"isConstructor": false,
"kind": "function",
"modifiers": [
null
],
"modifiers": [null],
"name": "_msgData",
"overrides": null,
"scope": 3618,
@ -452,9 +416,7 @@
"children": [
{
"attributes": {
"parameters": [
null
]
"parameters": [null]
},
"children": [],
"id": 3607,
@ -503,9 +465,7 @@
{
"attributes": {
"argumentTypes": null,
"overloadedDeclarations": [
null
],
"overloadedDeclarations": [null],
"referencedDeclaration": -28,
"type": "contract Context",
"value": "this"
@ -539,9 +499,7 @@
{
"attributes": {
"argumentTypes": null,
"overloadedDeclarations": [
null
],
"overloadedDeclarations": [null],
"referencedDeclaration": -15,
"type": "msg",
"value": "msg"
@ -597,4 +555,4 @@
"methods": {},
"version": 1
}
}
}

View File

@ -196,9 +196,7 @@
"ast": {
"absolutePath": "@openzeppelin/contracts/token/ERC20/IERC20.sol",
"exportedSymbols": {
"IERC20": [
3086
]
"IERC20": [3086]
},
"id": 3087,
"license": "MIT",
@ -206,15 +204,7 @@
"nodes": [
{
"id": 3010,
"literals": [
"solidity",
">=",
"0.6",
".0",
"<",
"0.8",
".0"
],
"literals": ["solidity", ">=", "0.6", ".0", "<", "0.8", ".0"],
"nodeType": "PragmaDirective",
"src": "33:31:6"
},
@ -231,9 +221,7 @@
},
"fullyImplemented": false,
"id": 3086,
"linearizedBaseContracts": [
3086
],
"linearizedBaseContracts": [3086],
"name": "IERC20",
"nodeType": "ContractDefinition",
"nodes": [
@ -1136,24 +1124,14 @@
"attributes": {
"absolutePath": "@openzeppelin/contracts/token/ERC20/IERC20.sol",
"exportedSymbols": {
"IERC20": [
3086
]
"IERC20": [3086]
},
"license": "MIT"
},
"children": [
{
"attributes": {
"literals": [
"solidity",
">=",
"0.6",
".0",
"<",
"0.8",
".0"
]
"literals": ["solidity", ">=", "0.6", ".0", "<", "0.8", ".0"]
},
"id": 3010,
"name": "PragmaDirective",
@ -1162,17 +1140,11 @@
{
"attributes": {
"abstract": false,
"baseContracts": [
null
],
"contractDependencies": [
null
],
"baseContracts": [null],
"contractDependencies": [null],
"contractKind": "interface",
"fullyImplemented": false,
"linearizedBaseContracts": [
3086
],
"linearizedBaseContracts": [3086],
"name": "IERC20",
"scope": 3087
},
@ -1192,9 +1164,7 @@
"implemented": false,
"isConstructor": false,
"kind": "function",
"modifiers": [
null
],
"modifiers": [null],
"name": "totalSupply",
"overrides": null,
"scope": 3086,
@ -1213,9 +1183,7 @@
},
{
"attributes": {
"parameters": [
null
]
"parameters": [null]
},
"children": [],
"id": 3013,
@ -1269,9 +1237,7 @@
"implemented": false,
"isConstructor": false,
"kind": "function",
"modifiers": [
null
],
"modifiers": [null],
"name": "balanceOf",
"overrides": null,
"scope": 3086,
@ -1371,9 +1337,7 @@
"implemented": false,
"isConstructor": false,
"kind": "function",
"modifiers": [
null
],
"modifiers": [null],
"name": "transfer",
"overrides": null,
"scope": 3086,
@ -1501,9 +1465,7 @@
"implemented": false,
"isConstructor": false,
"kind": "function",
"modifiers": [
null
],
"modifiers": [null],
"name": "allowance",
"overrides": null,
"scope": 3086,
@ -1632,9 +1594,7 @@
"implemented": false,
"isConstructor": false,
"kind": "function",
"modifiers": [
null
],
"modifiers": [null],
"name": "approve",
"overrides": null,
"scope": 3086,
@ -1762,9 +1722,7 @@
"implemented": false,
"isConstructor": false,
"kind": "function",
"modifiers": [
null
],
"modifiers": [null],
"name": "transferFrom",
"overrides": null,
"scope": 3086,
@ -2199,4 +2157,4 @@
"methods": {},
"version": 1
}
}
}

View File

@ -12,9 +12,7 @@
"ast": {
"absolutePath": "@openzeppelin/contracts/utils/ReentrancyGuard.sol",
"exportedSymbols": {
"ReentrancyGuard": [
3658
]
"ReentrancyGuard": [3658]
},
"id": 3659,
"license": "MIT",
@ -22,15 +20,7 @@
"nodes": [
{
"id": 3620,
"literals": [
"solidity",
">=",
"0.6",
".0",
"<",
"0.8",
".0"
],
"literals": ["solidity", ">=", "0.6", ".0", "<", "0.8", ".0"],
"nodeType": "PragmaDirective",
"src": "33:31:10"
},
@ -47,9 +37,7 @@
},
"fullyImplemented": true,
"id": 3658,
"linearizedBaseContracts": [
3658
],
"linearizedBaseContracts": [3658],
"name": "ReentrancyGuard",
"nodeType": "ContractDefinition",
"nodes": [
@ -339,10 +327,7 @@
"id": 3640,
"name": "require",
"nodeType": "Identifier",
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"src": "2269:7:10",
"typeDescriptions": {
@ -499,24 +484,14 @@
"attributes": {
"absolutePath": "@openzeppelin/contracts/utils/ReentrancyGuard.sol",
"exportedSymbols": {
"ReentrancyGuard": [
3658
]
"ReentrancyGuard": [3658]
},
"license": "MIT"
},
"children": [
{
"attributes": {
"literals": [
"solidity",
">=",
"0.6",
".0",
"<",
"0.8",
".0"
]
"literals": ["solidity", ">=", "0.6", ".0", "<", "0.8", ".0"]
},
"id": 3620,
"name": "PragmaDirective",
@ -525,17 +500,11 @@
{
"attributes": {
"abstract": true,
"baseContracts": [
null
],
"contractDependencies": [
null
],
"baseContracts": [null],
"contractDependencies": [null],
"contractKind": "contract",
"fullyImplemented": true,
"linearizedBaseContracts": [
3658
],
"linearizedBaseContracts": [3658],
"name": "ReentrancyGuard",
"scope": 3659
},
@ -670,9 +639,7 @@
"implemented": true,
"isConstructor": true,
"kind": "constructor",
"modifiers": [
null
],
"modifiers": [null],
"name": "",
"overrides": null,
"scope": 3658,
@ -683,9 +650,7 @@
"children": [
{
"attributes": {
"parameters": [
null
]
"parameters": [null]
},
"children": [],
"id": 3630,
@ -694,9 +659,7 @@
},
{
"attributes": {
"parameters": [
null
]
"parameters": [null]
},
"children": [],
"id": 3631,
@ -721,9 +684,7 @@
{
"attributes": {
"argumentTypes": null,
"overloadedDeclarations": [
null
],
"overloadedDeclarations": [null],
"referencedDeclaration": 3629,
"type": "uint256",
"value": "_status"
@ -735,9 +696,7 @@
{
"attributes": {
"argumentTypes": null,
"overloadedDeclarations": [
null
],
"overloadedDeclarations": [null],
"referencedDeclaration": 3624,
"type": "uint256",
"value": "_NOT_ENTERED"
@ -784,9 +743,7 @@
},
{
"attributes": {
"parameters": [
null
]
"parameters": [null]
},
"children": [],
"id": 3639,
@ -805,9 +762,7 @@
"isPure": false,
"isStructConstructorCall": false,
"lValueRequested": false,
"names": [
null
],
"names": [null],
"tryCall": false,
"type": "tuple()",
"type_conversion": false
@ -825,10 +780,7 @@
"typeString": "literal_string \"ReentrancyGuard: reentrant call\""
}
],
"overloadedDeclarations": [
-18,
-18
],
"overloadedDeclarations": [-18, -18],
"referencedDeclaration": -18,
"type": "function (bool,string memory) pure",
"value": "require"
@ -855,9 +807,7 @@
{
"attributes": {
"argumentTypes": null,
"overloadedDeclarations": [
null
],
"overloadedDeclarations": [null],
"referencedDeclaration": 3629,
"type": "uint256",
"value": "_status"
@ -869,9 +819,7 @@
{
"attributes": {
"argumentTypes": null,
"overloadedDeclarations": [
null
],
"overloadedDeclarations": [null],
"referencedDeclaration": 3627,
"type": "uint256",
"value": "_ENTERED"
@ -928,9 +876,7 @@
{
"attributes": {
"argumentTypes": null,
"overloadedDeclarations": [
null
],
"overloadedDeclarations": [null],
"referencedDeclaration": 3629,
"type": "uint256",
"value": "_status"
@ -942,9 +888,7 @@
{
"attributes": {
"argumentTypes": null,
"overloadedDeclarations": [
null
],
"overloadedDeclarations": [null],
"referencedDeclaration": 3627,
"type": "uint256",
"value": "_ENTERED"
@ -984,9 +928,7 @@
{
"attributes": {
"argumentTypes": null,
"overloadedDeclarations": [
null
],
"overloadedDeclarations": [null],
"referencedDeclaration": 3629,
"type": "uint256",
"value": "_status"
@ -998,9 +940,7 @@
{
"attributes": {
"argumentTypes": null,
"overloadedDeclarations": [
null
],
"overloadedDeclarations": [null],
"referencedDeclaration": 3624,
"type": "uint256",
"value": "_NOT_ENTERED"
@ -1057,4 +997,4 @@
"methods": {},
"version": 1
}
}
}

View File

@ -1,15 +1,31 @@
{
"name": "@solana/bridge-sdk",
"version": "0.0.1",
"description": "Bridge common sdk utilities",
"main": "dist/lib/index.js",
"types": "dist/lib/index.d.ts",
"exports": {
".": "./dist/lib/"
},
"license": "Apache-2.0",
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=10"
},
"dependencies": {
"@solana/wallet-base": "0.0.1",
"@babel/preset-typescript": "^7.13.0",
"@oyster/common": "0.0.1",
"@solana/spl-token": "0.0.13",
"@solana/spl-token-swap": "0.1.0",
"@solana/web3.js": "^0.86.2",
"@solana/wallet-base": "0.0.1",
"@solana/wallet-ledger": "0.0.1",
"@solana/web3.js": "^1.5.0",
"bignumber.js": "^9.0.1",
"bn.js": "^5.1.3",
"bs58": "^4.0.1",
"buffer-layout": "^1.2.0",
"buffer-layout": "1.2.0",
"ethers": "^4.0.48",
"eventemitter3": "^4.0.7",
"lodash": "^4.17.20",
@ -17,9 +33,20 @@
"web3": "^1.3.0"
},
"scripts": {
"build": "tsc",
"start": "npm-run-all --parallel watch watch-css watch-css-src",
"watch-css": "less-watch-compiler src/ dist/lib/",
"watch-css-src": "less-watch-compiler src/ src/",
"watch": "tsc --watch",
"test": "jest test",
"clean": "rm -rf dist",
"prepare": "run-s clean build",
"format:fix": "prettier --write \"**/*.+(js|jsx|ts|tsx|json|css|md)\"",
"ethers": "typechain --target ethers-v4 --outDir src/contracts 'contracts/*.json'"
},
"files": [
"dist"
],
"browserslist": {
"production": [
">0.2%",
@ -36,6 +63,14 @@
"type": "git",
"url": "https://github.com/solana-labs/oyster"
},
"peerDependencies": {
"react": "*",
"react-dom": "*"
},
"resolutions": {
"react": "16.13.1",
"react-dom": "16.13.1"
},
"homepage": ".",
"devDependencies": {
"@typechain/ethers-v4": "^1.0.0",

View File

@ -0,0 +1,21 @@
// 40 - ExecutedVAA (claim)
import { publicKey } from '@oyster/common/dist/lib/utils/layout';
import * as BufferLayout from 'buffer-layout';
export const ClaimedVAA = BufferLayout.struct([
BufferLayout.blob(32, 'hash'),
BufferLayout.u32('vaaTime'),
BufferLayout.u8('initialized'),
//BufferLayout.seq(BufferLayout.u8(), 3),
]);
/*
pub struct ClaimedVAA {
/// hash of the vaa
pub hash: [u8; 32],
/// time the vaa was submitted
pub vaa_time: u32,
/// Is `true` if this structure has been initialized.
pub is_initialized: bool,
}
*/

View File

@ -1,10 +1,10 @@
// 44 - bridge config
import { Layout } from '@oyster/common';
import { publicKey } from '@oyster/common/dist/lib/utils/layout';
import * as BufferLayout from 'buffer-layout';
export const BridgeLayout: typeof BufferLayout.Structure = BufferLayout.struct([
export const BridgeLayout = BufferLayout.struct([
BufferLayout.u32('guardianSetIndex'),
BufferLayout.u8('guardianSetExpirationTime'),
Layout.publicKey('tokenProgram'),
publicKey('tokenProgram'),
BufferLayout.u8('isInitialized'),
]);

View File

@ -1,7 +1,7 @@
import { programIds, sendTransaction } from '@oyster/common';
import { programIds, sendTransactionWithRetry, sleep } from '@oyster/common';
import { WalletAdapter } from '@solana/wallet-base';
import { ethers } from 'ethers';
import { WormholeFactory } from '../../../contracts/WormholeFactory';
import { WormholeFactory } from '../../contracts/WormholeFactory';
import { bridgeAuthorityKey } from './../helpers';
import { Connection, PublicKey, SystemProgram } from '@solana/web3.js';
import { Token } from '@solana/spl-token';
@ -9,6 +9,7 @@ import { ProgressUpdate, TransferRequest } from './interface';
import BN from 'bn.js';
import { createLockAssetInstruction } from '../lock';
import { TransferOutProposalLayout } from '../transferOutProposal';
import { SolanaBridge } from '../../core';
export const fromSolana = async (
connection: Connection,
@ -16,17 +17,17 @@ export const fromSolana = async (
request: TransferRequest,
provider: ethers.providers.Web3Provider,
setProgress: (update: ProgressUpdate) => void,
bridge?: SolanaBridge,
) => {
if (
!request.asset ||
!request.amount ||
!request.recipient ||
!request.to ||
!request.info
!request.info ||
!bridge
) {
return;
}
const walletName = 'MetaMask';
const signer = provider?.getSigner();
request.recipient = Buffer.from((await signer.getAddress()).slice(2), 'hex');
const nonce = await provider.getTransactionCount(
@ -34,11 +35,6 @@ export const fromSolana = async (
'pending',
);
const amountBN = ethers.utils.parseUnits(
request.amount.toString(),
request.info.decimals,
);
let counter = 0;
// check difference between lock/approve (invoke lock if allowance < amount)
const steps = {
@ -66,7 +62,7 @@ export const fromSolana = async (
return;
}
let group = 'Lock assets';
let group = 'Initiate transfer';
const programs = programIds();
const bridgeId = programs.wormhole.pubkey;
const authorityKey = await bridgeAuthorityKey(bridgeId);
@ -79,7 +75,7 @@ export const fromSolana = async (
wallet.publicKey,
new PublicKey(request.info.address),
new PublicKey(request.info.mint),
new BN(request.amount.toString()),
new BN(amount),
request.to,
request.recipient,
{
@ -100,18 +96,34 @@ export const fromSolana = async (
amount,
);
setProgress({
message: 'Waiting for Solana approval...',
type: 'user',
group,
step: counter++,
});
let fee_ix = SystemProgram.transfer({
fromPubkey: wallet.publicKey,
toPubkey: authorityKey,
lamports: await getTransferFee(connection),
});
const { slot } = await sendTransaction(
const { slot } = await sendTransactionWithRetry(
connection,
wallet,
[ix, fee_ix, lock_ix],
[],
true,
undefined,
false,
undefined,
() => {
setProgress({
message: 'Executing Solana Transaction',
type: 'wait',
group,
step: counter++,
});
},
);
return steps.wait(request, transferKey, slot);
@ -123,32 +135,39 @@ export const fromSolana = async (
) => {
return new Promise<void>((resolve, reject) => {
let completed = false;
let unsubscribed = false;
let startSlot = slot;
let group = 'Lock assets';
const solConfirmationMessage = (current: number) =>
`Awaiting Solana confirmations: ${current} out of 32`;
let replaceMessage = false;
let slotUpdateListener = connection.onSlotChange(slot => {
if (completed) return;
const passedSlots = slot.slot - startSlot;
if (unsubscribed) {
return;
}
const passedSlots = Math.min(Math.max(slot.slot - startSlot, 0), 32);
const isLast = passedSlots - 1 === 31;
if (passedSlots < 32) {
// setLoading({
// loading: true,
// message: "Awaiting confirmations",
// progress: {
// completion: (slot.slot - startSlot) / 32 * 100,
// content: `${slot.slot - startSlot}/${32}`
// }
// })
// setProgress({
// message: ethConfirmationMessage(passedBlocks),
// type: isLast ? 'done' : 'wait',
// step: counter++,
// group,
// replace: passedBlocks > 0,
// });
} else {
//setLoading({loading: true, message: "Awaiting guardian confirmation"})
if (passedSlots <= 32) {
setProgress({
message: solConfirmationMessage(passedSlots),
type: isLast ? 'done' : 'wait',
step: counter++,
group,
replace: replaceMessage,
});
replaceMessage = true;
}
if (completed || isLast) {
unsubscribed = true;
setProgress({
message: 'Awaiting guardian confirmation. (Up to few min.)',
type: 'wait',
step: counter++,
group,
});
}
});
@ -175,28 +194,37 @@ export const fromSolana = async (
completed = true;
connection.removeAccountChangeListener(accountChangeListener);
connection.removeSlotChangeListener(slotUpdateListener);
let signatures;
// let signatures = await bridge.fetchSignatureStatus(
// lockup.signatureAccount,
// );
// let sigData = Buffer.of(
// ...signatures.reduce((previousValue, currentValue) => {
// previousValue.push(currentValue.index);
// previousValue.push(...currentValue.signature);
while (!signatures) {
try {
signatures = await bridge.fetchSignatureStatus(
lockup.signatureAccount,
);
break;
} catch {
await sleep(500);
}
}
// return previousValue;
// }, new Array<number>()),
// );
let sigData = Buffer.of(
...signatures.reduce((previousValue, currentValue) => {
previousValue.push(currentValue.index);
previousValue.push(...currentValue.signature);
return previousValue;
}, new Array<number>()),
);
vaa = Buffer.concat([
vaa.slice(0, 5),
Buffer.of(signatures.length),
sigData,
vaa.slice(6),
]);
// vaa = Buffer.concat([
// vaa.slice(0, 5),
// Buffer.of(signatures.length),
// sigData,
// vaa.slice(6),
// ]);
// transferVAA = vaa
try {
await steps.postVAA(request);
await steps.postVAA(request, vaa);
resolve();
} catch {
reject();
@ -206,22 +234,30 @@ export const fromSolana = async (
);
});
},
postVAA: async (request: TransferRequest) => {
postVAA: async (request: TransferRequest, vaa: any) => {
let wh = WormholeFactory.connect(programIds().wormhole.bridge, signer);
// setLoading({
// ...loading,
// loading: true,
// message: "Sign the claim...",
// })
// let tx = await wh.submitVAA(vaa);
// setLoading({
// ...loading,
// loading: true,
// message: "Waiting for tokens unlock to be mined...",
// })
// await tx.wait(1);
// message.success({content: "Execution of VAA succeeded", key: "eth_tx"})
let group = 'Finalizing transfer';
setProgress({
message: 'Sign the claim...',
type: 'wait',
group,
step: counter++,
});
let tx = await wh.submitVAA(vaa);
setProgress({
message: 'Waiting for tokens unlock to be mined... (Up to few min.)',
type: 'wait',
group,
step: counter++,
});
await tx.wait(1);
setProgress({
message: 'Execution of VAA succeeded',
type: 'done',
group,
step: counter++,
});
//message.success({content: "", key: "eth_tx"})
},
};

View File

@ -1,6 +1,5 @@
import BN from 'bn.js';
import { BigNumber } from 'bignumber.js';
import { ethers } from 'ethers';
import { BigNumber } from 'ethers/utils';
import { ASSET_CHAIN } from '../constants';
export interface ProgressUpdate {
@ -16,7 +15,7 @@ export interface TransferRequestInfo {
name: string;
balance: BigNumber;
decimals: number;
allowance: BigNumber;
allowance: ethers.utils.BigNumber;
isWrapped: boolean;
chainID: number;
assetAddress: Buffer;
@ -37,12 +36,10 @@ export interface TransferRequest {
export const displayBalance = (info?: TransferRequestInfo) => {
try {
return (
new BN(info?.balance?.toString() || 0)
.div(new BN(10).pow(new BN(Math.min((info?.decimals || 0) - 2, 0))))
.toNumber() / 100
);
} catch {
const balance = info?.balance || new BigNumber(0);
const precision = new BigNumber(10).pow(info?.decimals || new BigNumber(0));
return balance.div(precision).toNumber();
} catch (e) {
return 0;
}
};

View File

@ -1,15 +1,15 @@
import {
programIds,
getMultipleAccounts,
sendTransaction,
sendTransactionWithRetry,
cache,
TokenAccountParser,
ParsedAccount,
createAssociatedTokenAccountInstruction,
} from '@oyster/common';
import { ethers } from 'ethers';
import { ERC20Factory } from '../../../contracts/ERC20Factory';
import { WormholeFactory } from '../../../contracts/WormholeFactory';
import { ERC20Factory } from '../../contracts/ERC20Factory';
import { WormholeFactory } from '../../contracts/WormholeFactory';
import { AssetMeta, createWrappedAssetInstruction } from './../meta';
import { bridgeAuthorityKey, wrappedAssetMintKey } from './../helpers';
import {
@ -21,6 +21,7 @@ import {
import { AccountInfo } from '@solana/spl-token';
import { TransferRequest, ProgressUpdate } from './interface';
import { WalletAdapter } from '@solana/wallet-base';
import { BigNumber } from 'bignumber.js';
export const toSolana = async (
connection: Connection,
@ -39,6 +40,9 @@ export const toSolana = async (
signer.getAddress(),
'pending',
);
const amountBigNumber = new BigNumber(request.amount.toString()).toFormat(
request.info.decimals,
);
const amountBN = ethers.utils.parseUnits(
request.amount.toString(),
@ -139,12 +143,11 @@ export const toSolana = async (
step: counter++,
});
await sendTransaction(
await sendTransactionWithRetry(
connection,
wallet,
instructions,
signers,
true,
);
}
} catch (err) {
@ -177,7 +180,7 @@ export const toSolana = async (
});
let res = await e.approve(programIds().wormhole.bridge, amountBN);
setProgress({
message: 'Waiting for ETH transaction to be mined...',
message: 'Waiting for ETH transaction to be mined... (Up to few min.)',
type: 'wait',
group,
step: counter++,
@ -240,7 +243,7 @@ export const toSolana = async (
false,
);
setProgress({
message: 'Waiting for ETH transaction to be mined...',
message: 'Waiting for ETH transaction to be mined... (Up to few min.)',
type: 'wait',
group,
step: counter++,

View File

@ -5,6 +5,17 @@ import assert from 'assert';
// @ts-ignore
import * as BufferLayout from 'buffer-layout';
import * as bs58 from 'bs58';
import { AssetMeta } from '../bridge';
export enum LockupStatus {
AWAITING_VAA,
UNCLAIMED_VAA,
COMPLETED,
}
export interface LockupWithStatus extends Lockup {
status: LockupStatus;
}
export interface Lockup {
lockupAddress: PublicKey;
@ -71,7 +82,40 @@ class SolanaBridge {
data,
});
}
// fetchAssetMeta fetches the AssetMeta for an SPL token
async fetchAssetMeta(mint: PublicKey): Promise<AssetMeta> {
// @ts-ignore
let configKey = await this.getConfigKey();
let seeds: Array<Buffer> = [
Buffer.from('meta'),
configKey.toBuffer(),
mint.toBuffer(),
];
// @ts-ignore
let metaKey = (
await solanaWeb3.PublicKey.findProgramAddress(seeds, this.programID)
)[0];
let metaInfo = await this.connection.getAccountInfo(metaKey);
if (metaInfo == null || metaInfo.lamports == 0) {
return {
address: mint.toBuffer(),
chain: CHAIN_ID_SOLANA,
decimals: 0,
};
} else {
const dataLayout = BufferLayout.struct([
BufferLayout.u8('assetChain'),
BufferLayout.blob(32, 'assetAddress'),
]);
let wrappedMeta = dataLayout.decode(metaInfo?.data);
return {
address: wrappedMeta.assetAddress,
chain: wrappedMeta.assetChain,
decimals: 0,
};
}
}
// fetchSignatureStatus fetches the signatures for a VAA
async fetchSignatureStatus(signatureStatus: PublicKey): Promise<Signature[]> {
let signatureInfo = await this.connection.getAccountInfo(

View File

@ -1 +1,10 @@
// TODO: move bridge interaction code to this library
export * as bridge from './bridge';
export * from './bridge';
export * as core from './core';
export * from './core';
export * from './contracts/ERC20Factory';
export * from './contracts/IERC20Factory';
export * from './contracts/WETHFactory';
export * from './contracts/WormholeFactory';
export * from './contracts/WrappedAssetFactory';

View File

@ -0,0 +1,4 @@
declare module 'buffer-layout' {
const bl: any;
export = bl;
}

View File

@ -1,22 +1,23 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"module": "commonjs",
"target": "es2019",
"moduleResolution": "node",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"allowJs": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"downlevelIteration": true,
"noEmit": true,
"jsx": "react",
"typeRoots": ["types", "../../types", "../../node_modules/@types"]
"typeRoots": ["types", "../../types", "../../node_modules/@types"],
"outDir": "./dist/lib",
"rootDir": "./src",
"composite": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src"]
"include": ["src/**/*"]
}

View File

@ -1,4 +1,5 @@
@surge-20: #00CC82;
@surge-30: #00B372;
@tungsten-100: #06101a;
@tungsten-80: #2F506F;
@tungsten-60: #547595;

View File

@ -13,7 +13,9 @@
"@solana/spl-token-registry": "^0.2.0",
"@solana/spl-token-swap": "0.1.0",
"@solana/wallet-base": "0.0.1",
"@solana/web3.js": "^0.86.2",
"@solana/wallet-ledger": "0.0.1",
"@solana/bridge-sdk": "0.0.1",
"@solana/web3.js": "^1.5.0",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
@ -36,6 +38,7 @@
"@web3-react/walletlink-connector": "^6.0.9",
"@welldone-software/why-did-you-render": "^6.0.5",
"animejs": "^3.2.1",
"bignumber.js": "^9.0.1",
"bn.js": "^5.1.3",
"bs58": "^4.0.1",
"buffer-layout": "^1.2.0",
@ -47,6 +50,7 @@
"eventemitter3": "^4.0.7",
"fortmatic": "^2.2.1",
"identicon.js": "^2.3.3",
"javascript-time-ago": "^2.3.4",
"jazzicon": "^1.5.0",
"lodash": "^4.17.20",
"react": "16.13.1",
@ -97,13 +101,14 @@
"@types/bn.js": "^5.1.0",
"@types/bs58": "^4.0.1",
"@types/identicon.js": "^2.3.0",
"@types/javascript-time-ago": "^2.0.2",
"@types/jest": "^24.9.1",
"@types/node": "^12.12.62",
"arweave-deploy": "^1.9.1",
"gh-pages": "^3.1.0",
"npm-link-shared": "0.5.6",
"typechain": "^4.0.3",
"prettier": "^2.1.2"
"prettier": "^2.1.2",
"typechain": "^4.0.3"
},
"peerDependencies": {
"react": "*",

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 111 KiB

View File

@ -1,50 +1,44 @@
<svg width="816" height="176" viewBox="0 0 816 176" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="816" height="87" viewBox="0 0 816 87" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_b)">
<path d="M424.384 172L426.282 169.322L410.202 95L388.316 95L376.927 140.419L365.425 95L343.539 95L327.459 169.322L329.358 172H340.524L354.147 107.275H355.599L370.227 164.188H383.515L398.143 107.275H399.594L413.217 172H424.384Z" fill="url(#paint0_linear)"/>
<path d="M0 97.6783L1.89829 95.0001H13.0647L26.6877 159.725H28.1393L42.7673 102.812H56.0554L70.6833 159.725H72.135L85.758 95.0001H96.9244L98.8227 97.6783L82.743 172H60.8569L49.3555 126.581L37.9658 172H16.0796L0 97.6783Z" fill="url(#paint1_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M241.862 172V95.0001H283.341C295.4 95.0001 301.43 101.026 301.43 113.078V125.688C301.43 135.137 297.708 140.865 290.264 142.874L303.663 169.099L301.988 172H290.375L276.082 143.767H255.485V172H241.862ZM255.485 131.491H282.894C286.169 131.491 287.807 129.855 287.807 126.581V112.186C287.807 108.912 286.169 107.275 282.894 107.275H255.485V131.491Z" fill="url(#paint2_linear)"/>
<path d="M460.199 95.0001V172H473.822V138.187H514.906V172H528.529V95.0001H514.906V125.912H473.822V95.0001H460.199Z" fill="url(#paint3_linear)"/>
<path d="M673.495 172V95.0001H687.117V159.725H721.957V172H673.495Z" fill="url(#paint4_linear)"/>
<path d="M755.598 95.0001V172H816V159.725H769.221V138.187H798.827V125.912H769.221V107.275H816V95.0001H755.598Z" fill="url(#paint5_linear)"/>
<path d="M424.384 82.9999L426.282 80.3217L410.202 6.00001L388.316 6.00001L376.927 51.4188L365.425 6L343.539 6L327.459 80.3217L329.358 82.9999H340.524L354.147 18.2754H355.599L370.227 75.1883H383.515L398.143 18.2754H399.594L413.217 82.9999H424.384Z" fill="url(#paint0_linear)"/>
<path d="M0 8.67833L1.89829 6.00007H13.0647L26.6877 70.7246H28.1393L42.7673 13.8117H56.0554L70.6833 70.7246H72.135L85.758 6.00007H96.9244L98.8227 8.67833L82.743 83H60.8569L49.3555 37.5812L37.9658 83H16.0796L0 8.67833Z" fill="url(#paint1_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M241.862 83V6.00007H283.341C295.4 6.00007 301.43 12.0261 301.43 24.0783V36.6884C301.43 46.1367 297.708 51.8652 290.264 53.8739L303.663 80.0985L301.988 83H290.375L276.082 54.7667H255.485V83H241.862ZM255.485 42.4913H282.894C286.169 42.4913 287.807 40.8546 287.807 37.5812V23.1856C287.807 19.9121 286.169 18.2754 282.894 18.2754H255.485V42.4913Z" fill="url(#paint2_linear)"/>
<path d="M460.199 6.00007V83H473.822V49.187H514.906V83H528.529V6.00007H514.906V36.9116H473.822V6.00007H460.199Z" fill="url(#paint3_linear)"/>
<path d="M673.495 83V6.00007H687.117V70.7246H721.957V83H673.495Z" fill="url(#paint4_linear)"/>
<path d="M755.598 6.00007V83H816V70.7246H769.221V49.187H798.827V36.9116H769.221V18.2754H816V6.00007H755.598Z" fill="url(#paint5_linear)"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M159.495 106.073L141.269 124.091C136.572 128.735 136.572 136.265 141.269 140.909L159.495 158.927C164.192 163.571 171.808 163.571 176.505 158.927L194.731 140.909C199.428 136.265 199.428 128.735 194.731 124.091L176.505 106.073C171.808 101.429 164.192 101.429 159.495 106.073ZM131.549 114.482C121.484 124.433 121.484 140.567 131.549 150.518L149.775 168.537C159.84 178.488 176.16 178.488 186.225 168.537L204.451 150.518C214.516 140.567 214.516 124.433 204.451 114.482L186.225 96.4634C176.16 86.5122 159.84 86.5122 149.775 96.4634L131.549 114.482Z" fill="#DC1FFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M591.495 106.073L573.269 124.091C568.572 128.735 568.572 136.265 573.269 140.909L591.495 158.927C596.192 163.571 603.808 163.571 608.505 158.927L626.731 140.909C631.428 136.265 631.428 128.735 626.731 124.091L608.505 106.073C603.808 101.429 596.192 101.429 591.495 106.073ZM563.549 114.482C553.484 124.433 553.484 140.567 563.549 150.518L581.775 168.537C591.84 178.488 608.16 178.488 618.225 168.537L636.451 150.518C646.516 140.567 646.516 124.433 636.451 114.482L618.225 96.4634C608.16 86.5122 591.84 86.5122 581.775 96.4634L563.549 114.482Z" fill="#00FFA3"/>
<path d="M149.811 12H156.924V10.75H151.22V6.79545H156.47V5.54545H151.22V1.61364H156.833V0.363636H149.811V12ZM171.537 1.61364H175.196V12H176.605V1.61364H180.264V0.363636H171.537V1.61364ZM195.239 12H196.648V6.79545H202.852V12H204.261V0.363636H202.852V5.54545H196.648V0.363636H195.239V12ZM219.883 12H226.996V10.75H221.292V6.79545H226.542V5.54545H221.292V1.61364H226.905V0.363636H219.883V12ZM242.245 12H243.654V7.45455H246.2C246.302 7.45455 246.398 7.45455 246.495 7.44886L248.95 12H250.586L247.955 7.19318C249.438 6.6875 250.131 5.47727 250.131 3.93182C250.131 1.875 248.904 0.363636 246.177 0.363636H242.245V12ZM243.654 6.18182V1.61364H246.131C248.018 1.61364 248.745 2.53409 248.745 3.93182C248.745 5.32955 248.018 6.18182 246.154 6.18182H243.654ZM265.279 12H272.393V10.75H266.688V6.79545H271.938V5.54545H266.688V1.61364H272.302V0.363636H265.279V12ZM295.278 0.363636V7.95455C295.278 9.65909 294.108 10.8864 292.165 10.8864C290.221 10.8864 289.051 9.65909 289.051 7.95455V0.363636H287.642V8.06818C287.642 10.4545 289.426 12.2045 292.165 12.2045C294.903 12.2045 296.687 10.4545 296.687 8.06818V0.363636H295.278ZM312.301 0.363636V12H313.619V3.15909H313.733L317.369 12H318.642L322.279 3.15909H322.392V12H323.71V0.363636H322.029L318.074 10.0227H317.938L313.983 0.363636H312.301ZM359.863 9.95455H361.136V6.84091H364.249V5.56818H361.136V2.45455H359.863V5.56818H356.749V6.84091H359.863V9.95455ZM403.646 3.27273H405.01C404.947 1.51705 403.328 0.204545 401.078 0.204545C398.851 0.204545 397.101 1.5 397.101 3.45455C397.101 5.02273 398.237 5.95455 400.055 6.47727L401.487 6.88636C402.714 7.22727 403.805 7.65909 403.805 8.81818C403.805 10.0909 402.578 10.9318 400.964 10.9318C399.578 10.9318 398.351 10.3182 398.237 9H396.783C396.919 10.9091 398.464 12.2045 400.964 12.2045C403.646 12.2045 405.169 10.7273 405.169 8.84091C405.169 6.65909 403.101 5.95455 401.896 5.63636L400.714 5.31818C399.851 5.09091 398.464 4.63636 398.464 3.38636C398.464 2.27273 399.487 1.45455 401.033 1.45455C402.442 1.45455 403.51 2.125 403.646 3.27273ZM430.104 6.18182C430.104 2.5 427.945 0.204545 424.967 0.204545C421.99 0.204545 419.831 2.5 419.831 6.18182C419.831 9.86364 421.99 12.1591 424.967 12.1591C427.945 12.1591 430.104 9.86364 430.104 6.18182ZM428.74 6.18182C428.74 9.20455 427.081 10.8409 424.967 10.8409C422.854 10.8409 421.195 9.20455 421.195 6.18182C421.195 3.15909 422.854 1.52273 424.967 1.52273C427.081 1.52273 428.74 3.15909 428.74 6.18182ZM445.273 12H452.091V10.75H446.682V0.363636H445.273V12ZM468.285 12L469.455 8.70455H474.16L475.33 12H476.808L472.535 0.363636H471.08L466.808 12H468.285ZM469.898 7.45455L471.762 2.20455H471.853L473.717 7.45455H469.898ZM500.647 0.363636H499.261V9.52273H499.147L492.784 0.363636H491.42V12H492.829V2.86364H492.943L499.284 12H500.647V0.363636ZM516.744 12L517.915 8.70455H522.619L523.79 12H525.267L520.994 0.363636H519.54L515.267 12H516.744ZM518.358 7.45455L520.221 2.20455H520.312L522.176 7.45455H518.358ZM557.179 12H561.384C564.134 12 565.27 10.6591 565.27 8.90909C565.27 7.06818 563.998 6.06818 562.929 6V5.88636C563.929 5.61364 564.816 4.95455 564.816 3.45455C564.816 1.75 563.679 0.363636 561.248 0.363636H557.179V12ZM558.588 10.75V6.70455H561.452C562.975 6.70455 563.929 7.72727 563.929 8.90909C563.929 9.93182 563.225 10.75 561.384 10.75H558.588ZM558.588 5.47727V1.61364H561.248C562.793 1.61364 563.475 2.43182 563.475 3.45455C563.475 4.68182 562.475 5.47727 561.202 5.47727H558.588ZM580.386 12H581.795V7.45455H584.34C584.442 7.45455 584.539 7.45455 584.636 7.44886L587.09 12H588.727L586.096 7.19318C587.579 6.6875 588.272 5.47727 588.272 3.93182C588.272 1.875 587.045 0.363636 584.317 0.363636H580.386V12ZM581.795 6.18182V1.61364H584.272C586.158 1.61364 586.886 2.53409 586.886 3.93182C586.886 5.32955 586.158 6.18182 584.295 6.18182H581.795ZM604.829 0.363636H603.42V12H604.829V0.363636ZM624.045 12C627.591 12 629.591 9.79545 629.591 6.15909C629.591 2.54545 627.591 0.363636 624.204 0.363636H620.454V12H624.045ZM621.864 10.75V1.61364H624.114C626.841 1.61364 628.227 3.34091 628.227 6.15909C628.227 9 626.841 10.75 623.954 10.75H621.864ZM652.709 4H654.163C653.732 1.75 651.845 0.204545 649.391 0.204545C646.408 0.204545 644.3 2.5 644.3 6.18182C644.3 9.86364 646.391 12.1591 649.482 12.1591C652.254 12.1591 654.232 10.3125 654.232 7.43182V6.18182H649.8V7.43182H652.868C652.828 9.52273 651.453 10.8409 649.482 10.8409C647.323 10.8409 645.663 9.20455 645.663 6.18182C645.663 3.15909 647.323 1.52273 649.391 1.52273C651.073 1.52273 652.215 2.47159 652.709 4ZM669.445 12H676.559V10.75H670.854V6.79545H676.104V5.54545H670.854V1.61364H676.468V0.363636H669.445V12Z" fill="url(#paint6_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M159.495 17.0732L141.269 35.0915C136.572 39.7354 136.572 47.2646 141.269 51.9085L159.495 69.9268C164.192 74.5707 171.808 74.5707 176.505 69.9268L194.731 51.9085C199.428 47.2646 199.428 39.7354 194.731 35.0915L176.505 17.0732C171.808 12.4293 164.192 12.4293 159.495 17.0732ZM131.549 25.4817C121.484 35.4329 121.484 51.5671 131.549 61.5183L149.775 79.5366C159.84 89.4878 176.16 89.4878 186.225 79.5366L204.451 61.5183C214.516 51.5671 214.516 35.4329 204.451 25.4817L186.225 7.46342C176.16 -2.48781 159.84 -2.48781 149.775 7.46342L131.549 25.4817Z" fill="#DC1FFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M591.495 17.0732L573.269 35.0915C568.572 39.7354 568.572 47.2646 573.269 51.9085L591.495 69.9268C596.192 74.5707 603.808 74.5707 608.505 69.9268L626.731 51.9085C631.428 47.2646 631.428 39.7354 626.731 35.0915L608.505 17.0732C603.808 12.4293 596.192 12.4293 591.495 17.0732ZM563.549 25.4817C553.484 35.4329 553.484 51.5671 563.549 61.5183L581.775 79.5366C591.84 89.4878 608.16 89.4878 618.225 79.5366L636.451 61.5183C646.516 51.5671 646.516 35.4329 636.451 25.4817L618.225 7.46342C608.16 -2.48781 591.84 -2.48781 581.775 7.46342L563.549 25.4817Z" fill="#00FFA3"/>
<defs>
<filter id="filter0_b" x="-6" y="89" width="828" height="89" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<filter id="filter0_b" x="-6" y="0" width="828" height="89" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImage" stdDeviation="3"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur" result="shape"/>
</filter>
<linearGradient id="paint0_linear" x1="408" y1="67" x2="408" y2="193.791" gradientUnits="userSpaceOnUse">
<linearGradient id="paint0_linear" x1="408" y1="-22" x2="408" y2="104.791" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#010202" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="408" y1="67" x2="408" y2="193.791" gradientUnits="userSpaceOnUse">
<linearGradient id="paint1_linear" x1="408" y1="-22" x2="408" y2="104.791" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#010202" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint2_linear" x1="408" y1="67" x2="408" y2="193.791" gradientUnits="userSpaceOnUse">
<linearGradient id="paint2_linear" x1="408" y1="-22" x2="408" y2="104.791" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#010202" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint3_linear" x1="408" y1="67" x2="408" y2="193.791" gradientUnits="userSpaceOnUse">
<linearGradient id="paint3_linear" x1="408" y1="-22" x2="408" y2="104.791" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#010202" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint4_linear" x1="408" y1="67" x2="408" y2="193.791" gradientUnits="userSpaceOnUse">
<linearGradient id="paint4_linear" x1="408" y1="-22" x2="408" y2="104.791" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#010202" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint5_linear" x1="408" y1="67" x2="408" y2="193.791" gradientUnits="userSpaceOnUse">
<linearGradient id="paint5_linear" x1="408" y1="-22" x2="408" y2="104.791" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#010202" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint6_linear" x1="132.986" y1="16" x2="708.028" y2="16" gradientUnits="userSpaceOnUse">
<stop offset="0.130208" stop-color="#DC1FFF"/>
<stop offset="0.5" stop-color="#4DA9FF"/>
<stop offset="0.885417" stop-color="#00CC82"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -6,7 +6,7 @@ header.ant-layout-header.App-Bar {
width: 100%;
background: transparent;
display: flex;
justify-content: center;
justify-content: center !important;
height: 80px;
.nav-burger {
@ -30,6 +30,9 @@ header.ant-layout-header.App-Bar {
justify-content: center;
height: auto;
align-items: center;
button.app-bar-item {
padding: 0;
}
.app-bar-item {
cursor: pointer;
padding: 0 30px;
@ -69,7 +72,7 @@ header.ant-layout-header.App-Bar {
max-width: 240px;
height: 100%;
flex-direction: column;
justify-content: flex-start;
justify-content: flex-start !important;
padding: 0;
.nav-burger {
display: inline-block;

View File

@ -43,15 +43,17 @@ export const AppBar = (props: { isRoot?: boolean }) => {
<div className={`app-bar-inner ${showMobileMenu ? 'mobile-active' : ''}`}>
{!props.isRoot && (
<div className={`app-bar-item logo root-mobile`}>
<img alt="logo-bar" src={'/appbar/logo.svg'} />
<Link to="/">
<img alt="logo-bar" src={'/appbar/logo.svg'} />
</Link>
</div>
)}
<div className={`app-bar-item ${isActiveClass('move')}`}>
<Link to="/move">Bridge</Link>
</div>
<div className={`app-bar-item ${isActiveClass('faq')}`}>
<Link to="/faq">FAQ</Link>
</div>
{/*<div className={`app-bar-item ${isActiveClass('faq')}`}>*/}
{/* <Link to="/faq">FAQ</Link>*/}
{/*</div>*/}
<div className={`app-bar-item ${isActiveClass('proof-of-assets')}`}>
<Link to="/proof-of-assets">Proof-of-Assets</Link>
</div>

View File

@ -0,0 +1,45 @@
@import "_colors";
#recent-tx-container {
max-width: 70%;
margin: auto;
padding-bottom: 70px;
.description-text {
color: @tungsten-60
}
.ant-table-pagination.ant-pagination {
margin: 16px 100px;
}
.ant-table {
thead {
tr > th.ant-table-cell {
background-color: @tungsten-100;
border: none;
}
}
tbody > tr:nth-child(even) > td.ant-table-cell {
background-color: @tungsten-100;
border: none;
}
tbody > tr:nth-child(odd) > td.ant-table-cell {
background-color: @tungsten-50;
border: none;
}
}
}
@media screen and (max-width: 900px) {
#recent-tx-container {
max-width: 100%;
}
}
@media screen and (max-width: 1200px) {
#recent-tx-container {
max-width: 90%;
}
}

View File

@ -0,0 +1,134 @@
import { Table } from 'antd';
import React from 'react';
import './index.less';
import { Link } from 'react-router-dom';
import { TokenDisplay } from '../../components/TokenDisplay';
import { toChainSymbol } from '../../contexts/chainPair';
import { formatUSD, shortenAddress } from '@oyster/common';
import { useWormholeAccounts } from '../../hooks/useWormholeAccounts';
export const AssetsTable = () => {
const {
loading: loadingLockedAccounts,
externalAssets,
totalInUSD,
} = useWormholeAccounts();
const columns = [
{
title: 'Symbol',
dataIndex: 'symbol',
key: 'symbol',
render(text: string, record: any) {
return {
props: {
style: {},
},
children: (
<Link
to={`/move?from=${toChainSymbol(record.chain)}&token=${
record.symbol
}`}
>
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
{record.logo && (
<TokenDisplay logo={record.logo} chain={record.chain} />
)}{' '}
{record.symbol}
</span>
</Link>
),
};
},
},
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Amount',
dataIndex: 'amount',
key: 'amount',
},
{
title: 'Amount ($)',
dataIndex: 'amountInUSD',
key: 'amountInUSD',
},
{
title: 'Price',
dataIndex: 'price',
width: 100,
key: 'price',
render(text: string, record: any) {
return {
props: {
style: { textAlign: 'right' },
},
children: record.price ? formatUSD.format(record.price) : '--',
};
},
},
{
title: 'Asset Address',
dataIndex: 'address',
key: 'address',
render(text: string, record: any) {
return {
props: {
style: {},
},
children: (
<a href={record.explorer} target="_blank" rel="noopener noreferrer">
{shortenAddress(text, 6)}
</a>
),
};
},
},
{
title: 'Wrapped Address',
dataIndex: 'mintKey',
key: 'mintKey',
render(text: string, record: any) {
return {
props: {
style: {},
},
children: (
<a
href={record.wrappedExplorer}
target="_blank"
rel="noopener noreferrer"
>
{shortenAddress(text, 6)}
</a>
),
};
},
},
];
return (
<div id={'recent-tx-container'}>
<div className={'home-subtitle'}>Total Value Locked</div>
<div
className={'assets-total description-text'}
style={{ marginBottom: '70px', fontSize: '40px' }}
>
{formatUSD.format(totalInUSD)}
</div>
<Table
scroll={{
scrollToFirstRowOnChange: false,
x: 900,
}}
dataSource={externalAssets.filter(a => a.name)}
columns={columns}
loading={loadingLockedAccounts}
/>
</div>
);
};

View File

@ -0,0 +1,32 @@
import React from 'react';
import { useWallet, WALLET_PROVIDERS } from '@oyster/common';
import { shortenAddress } from '@oyster/common';
export const CurrentUserWalletBadge = (props: { showDisconnect?: boolean }) => {
const { wallet, disconnect } = useWallet();
if (!wallet || !wallet.publicKey) {
return null;
}
return (
<div className="wallet-wrapper">
<div className="wallet-key">
<img
alt={'icon'}
width={20}
height={20}
src={WALLET_PROVIDERS.filter(p => p.name === 'Sollet')[0]?.icon}
style={{ marginRight: 8 }}
/>
{shortenAddress(`${wallet.publicKey}`)}
{props.showDisconnect && (
<span className={'disconnect'} onClick={() => disconnect()}>
X
</span>
)}
</div>
</div>
);
};

View File

@ -56,7 +56,7 @@ export const EthereumConnect = () => {
</Dropdown.Button>
) : (
<Button onClick={() => onConnectEthereum && onConnectEthereum()}>
CONNECT WALLET
CONNECT
</Button>
)}
</div>

View File

@ -1,16 +1,18 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import {
ConnectButton,
CurrentUserWalletBadge,
CurrentUserBadge,
NumericInput,
useMint,
useUserAccounts,
useWallet,
} from '@oyster/common';
import './style.less';
import { ASSET_CHAIN } from '../../models/bridge/constants';
import { TokenSelectModal } from '../TokenSelectModal';
import { chainToName } from '../../utils/assets';
import { ASSET_CHAIN, chainToName } from '../../utils/assets';
import { TokenChain } from '../TokenDisplay/tokenChain';
import { EthereumConnect } from '../EthereumConnect';
import { CurrentUserWalletBadge } from '../CurrentUserWalletBadge';
export function Input(props: {
title: string;
@ -32,16 +34,14 @@ export function Input(props: {
<div className="input-chain">
<TokenChain chain={props.chain} className={'input-icon'} />
{chainToName(props.chain)}
{typeof props.balance === 'number' && (
<div
className="balance"
onClick={() =>
props.onInputChange && props.onInputChange(props.balance)
}
>
{props.balance.toFixed(6)}
</div>
)}
<div
className="balance"
onClick={() =>
props.onInputChange && props.onInputChange(props.balance)
}
>
{props.balance?.toFixed(6)}
</div>
</div>
<div className="input-container">
<NumericInput

View File

@ -181,6 +181,18 @@
}
}
.dashed-input-container.right {
& > button.ant-btn:not(.ant-dropdown-trigger) {
text-transform: uppercase;
color: white;
width: 166px;
font-size: 14px;
background: #E67828;
border-radius: 8px;
height: 40px;
}
}
.wallet-wrapper {
display: flex;
justify-content: center;

View File

@ -1,34 +1,18 @@
import React, { useEffect, useState } from 'react';
import React from 'react';
import './../../App.less';
import './index.less';
import { Layout, Button, Popover } from 'antd';
import { Link, useLocation } from 'react-router-dom';
import { Layout } from 'antd';
import { useLocation } from 'react-router-dom';
import { LABELS } from '../../constants';
import { AppBar } from '../AppBar';
import Wormhole from '../Wormhole';
import { Footer as AppFooter } from './../Footer';
import { EthereumConnect } from '../EthereumConnect';
import { useEthereum } from '../../contexts';
import { Settings } from '@oyster/common';
import { SettingOutlined } from '@ant-design/icons';
const { Header, Content, Footer } = Layout;
export const AppLayout = React.memo((props: any) => {
const { connected, disconnect } = useEthereum();
const location = useLocation();
const [wormholeReady, setWormholeReady] = useState(false);
const paths: { [key: string]: string } = {
'/faucet': '7',
};
const isRoot = location.pathname === '/';
const current =
[...Object.keys(paths)].find(key => location.pathname.startsWith(key)) ||
'';
return (
<>
<div className={`App`}>
@ -40,9 +24,10 @@ export const AppLayout = React.memo((props: any) => {
{props.children}
</Content>
<Footer>
<div className={'description-text'} style={{ color: '#2F506F' }}>
© Solana Foundation
</div>
<div
className={'description-text'}
style={{ color: '#2F506F' }}
></div>
</Footer>
</Layout>
</div>

View File

@ -0,0 +1,54 @@
@import "_colors";
#recent-tx-container {
max-width: 70%;
margin: auto;
padding-bottom: 70px;
.completed {
color: @surge-30;
}
.failed {
color: @tungsten-60;
}
.error {
color: #6E1080;
}
.description-text {
color: @tungsten-60
}
.ant-table-pagination.ant-pagination {
margin: 16px 100px;
}
.ant-table {
thead {
tr > th.ant-table-cell {
background-color: @tungsten-100;
border: none;
}
}
tbody > tr:nth-child(even) > td.ant-table-cell {
background-color: @tungsten-100;
border: none;
}
tbody > tr:nth-child(odd) > td.ant-table-cell {
background-color: @tungsten-50;
border: none;
}
}
}
@media screen and (max-width: 900px) {
#recent-tx-container {
max-width: 100%;
}
}
@media screen and (max-width: 1200px) {
#recent-tx-container {
max-width: 90%;
}
}

View File

@ -1,29 +1,56 @@
import { Table } from 'antd';
import React from 'react';
import { Button, Table, notification } from 'antd';
import React, { useEffect, useMemo, useState } from 'react';
import './index.less';
import TimeAgo from 'javascript-time-ago';
import en from 'javascript-time-ago/locale/en';
import { Link } from 'react-router-dom';
import { useWormholeAccounts } from '../../hooks/useWormholeAccounts';
import { TokenDisplay } from '../../components/TokenDisplay';
import { toChainSymbol } from '../../contexts/chainPair';
import { formatUSD, shortenAddress } from '@oyster/common';
import {
formatUSD,
shortenAddress,
Identicon,
programIds,
TokenAccount,
} from '@oyster/common';
import { useWormholeTransactions } from '../../hooks/useWormholeTransactions';
import { ASSET_CHAIN } from '../../utils/assets';
import { TokenChain } from '../TokenDisplay/tokenChain';
import bs58 from 'bs58';
import { SyncOutlined } from '@ant-design/icons';
import { typeToIcon } from '../Transfer';
import { ProgressUpdate } from '@solana/bridge-sdk';
import { WormholeFactory } from '@solana/bridge-sdk';
import { useEthereum } from '../../contexts';
import { useBridge } from '../../contexts/bridge';
export const RecentTransactionsTable = () => {
const {
loading: loadingLockedAccounts,
externalAssets,
totalInUSD,
} = useWormholeAccounts();
TimeAgo.addDefaultLocale(en);
const timeAgo = new TimeAgo('en-US');
const columns = [
export const RecentTransactionsTable = (props: {
showUserTransactions?: boolean;
tokenAccounts: TokenAccount[];
}) => {
const { loading: loadingTransfers, transfers } = useWormholeTransactions(
props.tokenAccounts,
);
const { provider } = useEthereum();
const bridge = useBridge();
const [completedVAAs, setCompletedVAAs] = useState<Array<string>>([]);
const baseColumns = [
{
title: 'Symbol',
dataIndex: 'symbol',
key: 'symbol',
title: '',
dataIndex: 'logo',
key: 'logo',
render(text: string, record: any) {
return {
props: {
style: {},
},
children: (
props: { style: {} },
children: record.logo ? (
<Link
to={`/move?from=${toChainSymbol(record.chain)}&token=${
record.symbol
@ -32,52 +59,79 @@ export const RecentTransactionsTable = () => {
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
{record.logo && (
<TokenDisplay logo={record.logo} chain={record.chain} />
)}{' '}
{record.symbol}
)}
</span>
</Link>
) : (
<div className="token-chain-logo">
<Identicon
style={{ width: '50' }}
address={
record.chain === ASSET_CHAIN.Solana
? record.address
: bs58.encode(Buffer.from(record.address))
}
/>
<TokenChain chain={record.chain} className={'chain-logo'} />
</div>
),
};
},
},
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Amount',
dataIndex: 'amount',
key: 'amount',
},
{
title: 'Amount ($)',
dataIndex: 'amountInUSD',
key: 'amountInUSD',
},
{
title: 'Price',
dataIndex: 'price',
width: 100,
key: 'price',
title: 'Asset',
dataIndex: 'symbol',
key: 'symbol',
render(text: string, record: any) {
const urlText = record.symbol || record.address;
return {
props: {
style: { textAlign: 'right' },
},
children: record.price ? formatUSD.format(record.price) : '--',
props: { style: {} },
children:
record.lockup.assetChain === ASSET_CHAIN.Solana ? (
<a
href={`https://explorer.solana.com/address/${record.address}`}
// eslint-disable-next-line react/jsx-no-target-blank
target="_blank"
title={urlText}
>
{record.symbol || shortenAddress(urlText, 5)}
</a>
) : (
<a
href={`https://etherscan.io/address/${record.address}`}
// eslint-disable-next-line react/jsx-no-target-blank
target="_blank"
title={urlText}
>
{record.symbol || shortenAddress(urlText, 5)}
</a>
),
};
},
},
{
title: 'Asset Address',
dataIndex: 'address',
key: 'address',
title: 'Tokens moved',
dataIndex: 'amount',
key: 'amount',
},
{
title: '$, value',
dataIndex: 'value',
key: 'value',
render(text: string, record: any) {
return {
props: {
style: {},
},
props: { style: {} },
children: record.value ? formatUSD.format(record.value) : '--',
};
},
},
{
title: 'TX hash',
dataIndex: 'txhash',
key: 'txhash',
render(text: string, record: any) {
return {
props: { style: {} },
children: (
<a href={record.explorer} target="_blank" rel="noopener noreferrer">
{shortenAddress(text, 6)}
@ -87,41 +141,208 @@ export const RecentTransactionsTable = () => {
},
},
{
title: 'Wrapped Address',
dataIndex: 'mintKey',
key: 'mintKey',
title: 'Date',
dataIndex: 'date',
key: 'date',
render(text: string, record: any) {
return {
props: {
style: {},
},
children: (
<a
href={record.wrappedExplorer}
target="_blank"
rel="noopener noreferrer"
>
{shortenAddress(text, 6)}
</a>
),
props: { style: {} },
children: timeAgo.format(new Date(record.date * 1000)),
};
},
},
];
const userColumns = useMemo(
() => [
...baseColumns,
{
title: 'Status',
dataIndex: 'status',
key: 'status',
render(text: string, record: any) {
const status =
completedVAAs.indexOf(record.txhash) > 0
? 'Completed'
: record.status;
return {
props: { style: {} },
children: (
<>
<span className={`${record.status?.toLowerCase()}`}>
{status}
</span>
{status === 'Failed' ? (
<Button
onClick={() => {
const NotificationContent = () => {
const [activeSteps, setActiveSteps] = useState<
ProgressUpdate[]
>([]);
let counter = 0;
useEffect(() => {
(async () => {
const signer = provider?.getSigner();
if (!signer || !bridge) {
setActiveSteps([
...activeSteps,
{
message: 'Connect your Ethereum Wallet',
type: 'error',
group: 'error',
step: counter++,
},
]);
} else {
const lockup = record.lockup;
let vaa = lockup.vaa;
for (let i = vaa.length; i > 0; i--) {
if (vaa[i] == 0xff) {
vaa = vaa.slice(0, i);
break;
}
}
let signatures = await bridge.fetchSignatureStatus(
lockup.signatureAccount,
);
let sigData = Buffer.of(
...signatures.reduce(
(previousValue, currentValue) => {
previousValue.push(currentValue.index);
previousValue.push(
...currentValue.signature,
);
return previousValue;
},
new Array<number>(),
),
);
vaa = Buffer.concat([
vaa.slice(0, 5),
Buffer.of(signatures.length),
sigData,
vaa.slice(6),
]);
let wh = WormholeFactory.connect(
programIds().wormhole.bridge,
signer,
);
let group = 'Finalizing transfer';
setActiveSteps([
...activeSteps,
{
message: 'Sign the claim...',
type: 'wait',
group,
step: counter++,
},
]);
let tx = await wh.submitVAA(vaa);
setActiveSteps([
...activeSteps,
{
message:
'Waiting for tokens unlock to be mined... (Up to few min.)',
type: 'wait',
group,
step: counter++,
},
]);
await tx.wait(1);
setActiveSteps([
...activeSteps,
{
message: 'Execution of VAA succeeded',
type: 'done',
group,
step: counter++,
},
]);
setCompletedVAAs([
...completedVAAs,
record.txhash,
]);
}
})();
}, [setActiveSteps]);
return (
<div>
<div
style={{
textAlign: 'left',
display: 'flex',
flexDirection: 'column',
}}
>
{(() => {
let group = '';
return activeSteps.map((step, i) => {
let prevGroup = group;
group = step.group;
let newGroup = prevGroup !== group;
return (
<>
{newGroup && <span>{group}</span>}
<span style={{ marginLeft: 15 }}>
{typeToIcon(
step.type,
activeSteps.length - 1 === i,
)}{' '}
{step.message}
</span>
</>
);
});
})()}
</div>
</div>
);
};
notification.open({
message: '',
duration: 0,
placement: 'bottomLeft',
description: <NotificationContent />,
className: 'custom-class',
style: {
width: 500,
},
});
}}
shape="circle"
size="large"
type="text"
style={{ color: '#547595', fontSize: '18px' }}
title={'Retry Transaction'}
icon={<SyncOutlined />}
/>
) : null}
</>
),
};
},
},
],
[completedVAAs, bridge, provider],
);
return (
<div id={'recent-tx-container'}>
<div className={'home-subtitle'}>Recent Transactions</div>
<div className={'description-text'} style={{ marginBottom: '70px' }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<div className={'home-subtitle'} style={{ marginBottom: '70px' }}>
My Recent Transactions
<div>(selected token only)</div>
</div>
<Table
scroll={{
scrollToFirstRowOnChange: false,
x: 900,
}}
dataSource={externalAssets.filter(a => a.name)}
columns={columns}
loading={loadingLockedAccounts}
dataSource={transfers.sort((a, b) => b.date - a.date)}
columns={userColumns}
loading={loadingTransfers}
/>
</div>
);

View File

@ -3,7 +3,7 @@ import { TokenInfo } from '@solana/spl-token-registry';
import { debug } from 'console';
import React from 'react';
import { useEthereum } from '../../contexts';
import { ASSET_CHAIN } from '../../models/bridge/constants';
import { ASSET_CHAIN } from '../../utils/assets';
import './style.less';
import { TokenChain } from './tokenChain';

View File

@ -1,5 +1,5 @@
import React from 'react';
import { ASSET_CHAIN } from '../../models/bridge/constants';
import { ASSET_CHAIN } from '../../utils/assets';
export const TokenChain = (props: {
chain?: ASSET_CHAIN;

View File

@ -6,9 +6,9 @@ import './style.less';
import { Input, Modal } from 'antd';
import { useEthereum } from '../../contexts';
import { TokenDisplay } from '../TokenDisplay';
import { ASSET_CHAIN } from '../../models/bridge/constants';
import { ASSET_CHAIN } from '../../utils/assets';
import { useConnectionConfig } from '@oyster/common';
import { filterModalSolTokens } from '../../utils/assets';
import { filterModalEthTokens, filterModalSolTokens } from '../../utils/assets';
export const TokenSelectModal = (props: {
onSelectToken: (token: string) => void;
@ -24,7 +24,10 @@ export const TokenSelectModal = (props: {
const inputRef = useRef<Input>(null);
const tokens = useMemo(
() => [...ethTokens, ...filterModalSolTokens(solTokens)],
() => [
...filterModalEthTokens(ethTokens),
...filterModalSolTokens(solTokens),
],
[ethTokens, solTokens],
);
@ -33,7 +36,8 @@ export const TokenSelectModal = (props: {
return tokens.filter(token => {
return (
(token.tags?.indexOf('longList') || -1) < 0 &&
token.symbol.includes(search.toUpperCase())
(token.symbol.toLowerCase().includes(search.toLowerCase()) ||
token.name.toLowerCase().includes(search.toLowerCase()))
);
});
}

View File

@ -1,36 +1,24 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { notification, Spin, Button } from 'antd';
import {
contexts,
ConnectButton,
programIds,
notify,
cache,
useUserAccounts,
} from '@oyster/common';
import { contexts, TokenAccount, useUserAccounts } from '@oyster/common';
import { Input } from '../Input';
import './style.less';
import { ASSET_CHAIN, chainToName } from '../../utils/assets';
import {
bridgeAuthorityKey,
displayBalance,
fromSolana,
ProgressUpdate,
toSolana,
TransferRequest,
wrappedAssetMintKey,
} from '../../models/bridge';
} from '@solana/bridge-sdk';
import { useEthereum } from '../../contexts';
import { TokenDisplay } from '../TokenDisplay';
import { WrappedAssetFactory } from '../../contracts/WrappedAssetFactory';
import { WormholeFactory } from '../../contracts/WormholeFactory';
import BN from 'bn.js';
import { useTokenChainPairState } from '../../contexts/chainPair';
import { LABELS } from '../../constants';
import { useCorrectNetwork } from '../../hooks/useCorrectNetwork';
import { BigNumber } from 'ethers/utils';
import { RecentTransactionsTable } from '../RecentTransactionsTable';
import { useBridge } from '../../contexts/bridge';
const { useConnection } = contexts.Connection;
const { useWallet } = contexts.Wallet;
@ -53,8 +41,10 @@ export const typeToIcon = (type: string, isLast: boolean) => {
export const Transfer = () => {
const connection = useConnection();
const bridge = useBridge();
const { wallet, connected } = useWallet();
const { provider, tokenMap } = useEthereum();
const { userAccounts } = useUserAccounts();
const hasCorrespondingNetworks = useCorrectNetwork();
const {
A,
@ -63,6 +53,7 @@ export const Transfer = () => {
setMintAddress,
setLastTypedAccount,
} = useTokenChainPairState();
const [request, setRequest] = useState<TransferRequest>({
from: ASSET_CHAIN.Ethereum,
to: ASSET_CHAIN.Solana,
@ -90,7 +81,13 @@ export const Transfer = () => {
to: B.chain,
info: A.info,
});
}, [A, B, mintAddress]);
}, [A, B, mintAddress, A.info]);
const tokenAccounts = useMemo(
() =>
userAccounts.filter(u => u.info.mint.toBase58() === request.info?.mint),
[request.info?.mint],
);
return (
<>
@ -105,7 +102,9 @@ export const Transfer = () => {
onChain={(chain: ASSET_CHAIN) => {
const from = A.chain;
A.setChain(chain);
B.setChain(from);
if (B.chain === chain) {
B.setChain(from);
}
}}
onInputChange={amount => {
setLastTypedAccount(A.chain);
@ -139,7 +138,9 @@ export const Transfer = () => {
onChain={(chain: ASSET_CHAIN) => {
const to = B.chain;
B.setChain(chain);
A.setChain(to);
if (A.chain === chain) {
A.setChain(to);
}
}}
onInputChange={amount => {
setLastTypedAccount(B.chain);
@ -184,6 +185,7 @@ export const Transfer = () => {
setActiveSteps(steps);
},
bridge,
);
}
@ -293,7 +295,7 @@ export const Transfer = () => {
: LABELS.TRANSFER
: LABELS.SET_CORRECT_WALLET_NETWORK}
</Button>
<RecentTransactionsTable />
<RecentTransactionsTable tokenAccounts={tokenAccounts} />
</>
);
};

View File

@ -22,13 +22,35 @@
.exchange-card {
position: relative;
max-width: calc(100% - 460px);
max-width: 900px;
width: 100%;
margin: 167px auto 150px auto;
display: flex;
justify-content: space-between;
}
.action-button{
width: 240px;
height: 64px;
border-radius: 12px;
border: none;
background-color: @surge-20 !important;
color: white !important;
font-weight: bold;
font-size: 18px;
line-height: 23px;
text-align: center;
letter-spacing: 0.04em;
text-transform: uppercase;
font-feature-settings: 'ss02' on;
}
.action-button:hover {
background-color: @surge-30 !important;
}
.transfer-button{
display: flex;

View File

@ -1,5 +1,5 @@
import React, { createContext, FunctionComponent, useContext } from 'react';
import { SolanaBridge } from '../core';
import { SolanaBridge } from '@solana/bridge-sdk';
import {
useConnection,
useConnectionConfig,
@ -13,12 +13,15 @@ export const BridgeProvider: FunctionComponent = ({ children }) => {
const connection = useConnection();
const programs = utils.programIds();
/// let bridge = new SolanaBridge(endpoint, connection, programs.wormhole.pubkey, programs.token);
let bridge = new SolanaBridge(
endpoint,
connection,
programs.wormhole.pubkey,
programs.token,
);
return (
<BridgeContext.Provider value={undefined}>
{children}
</BridgeContext.Provider>
<BridgeContext.Provider value={bridge}>{children}</BridgeContext.Provider>
);
};

View File

@ -14,16 +14,24 @@ import {
useUserAccounts,
} from '@oyster/common';
import { TokenInfo } from '@solana/spl-token-registry';
import { ASSET_CHAIN, filterModalSolTokens } from '../utils/assets';
import {
ASSET_CHAIN,
filterModalEthTokens,
filterModalSolTokens,
} from '../utils/assets';
import { useEthereum } from './ethereum';
import { BigNumber } from 'ethers/utils';
import { WrappedAssetFactory } from '../contracts/WrappedAssetFactory';
import { WormholeFactory } from '../contracts/WormholeFactory';
import { BigNumber } from 'bignumber.js';
import { AssetMeta, WrappedAssetFactory } from '@solana/bridge-sdk';
import { WormholeFactory } from '@solana/bridge-sdk';
import {
bridgeAuthorityKey,
TransferRequestInfo,
wrappedAssetMintKey,
} from '../models/bridge';
} from '@solana/bridge-sdk';
import { useBridge } from './bridge';
import { PublicKey } from '@solana/web3.js';
import { ethers } from 'ethers';
export interface TokenChainContextState {
info?: TransferRequestInfo;
@ -91,11 +99,22 @@ export const useCurrencyLeg = (mintAddress: string) => {
const [chain, setChain] = useState(ASSET_CHAIN.Ethereum);
const [info, setInfo] = useState<TransferRequestInfo>();
const { userAccounts } = useUserAccounts();
const bridge = useBridge();
const { provider, tokens: ethTokens } = useEthereum();
const { tokens: solTokens } = useConnectionConfig();
const connection = useConnection();
const defaultCoinInfo = {
address: '',
name: '',
balance: new BigNumber(0),
decimals: 0,
allowance: new ethers.utils.BigNumber(0),
isWrapped: false,
chainID: 0,
assetAddress: new Buffer(0),
mint: '',
};
useEffect(() => {
if (!provider || !connection) {
return;
@ -103,31 +122,78 @@ export const useCurrencyLeg = (mintAddress: string) => {
(async () => {
const ethToken = ethTokens.find(t => t.address === mintAddress);
const solToken = solTokens.find(t => t.address === mintAddress);
let solToken = solTokens.find(t => t.address === mintAddress);
let mintKeyAddress = '';
let symbol = '';
let decimals = 0;
// eth assets on eth chain
// eth asset on sol chain
// sol asset on eth chain
// sol asset on sol chain
//console.log({ chain, solToken, ethToken });
if (chain === ASSET_CHAIN.Solana) {
if (!solToken && ethToken) {
try {
const bridgeId = programIds().wormhole.pubkey;
const authority = await bridgeAuthorityKey(bridgeId);
const assetAddress = Buffer.from(ethToken.address.slice(2), 'hex');
const meta: AssetMeta = {
decimals: Math.min(ethToken.decimals, 9),
address: assetAddress,
chain: ASSET_CHAIN.Ethereum,
};
const mintKey = await wrappedAssetMintKey(
bridgeId,
authority,
meta,
);
if (mintKey) {
mintKeyAddress = mintKey.toBase58();
solToken = solTokens.find(t => t.address === mintKeyAddress);
if (!solToken) {
symbol = ethToken.symbol;
decimals = ethToken.decimals;
}
} else {
setInfo(defaultCoinInfo);
return;
}
} catch {
setInfo(defaultCoinInfo);
return;
}
}
if (!solToken && (!symbol || !mintKeyAddress || !decimals)) {
setInfo(defaultCoinInfo);
return;
}
const currentAccount = userAccounts?.find(
a => a.info.mint.toBase58() === (solToken?.address || mintKeyAddress),
);
const assetMeta = await bridge?.fetchAssetMeta(
new PublicKey(solToken?.address || mintKeyAddress),
);
let ethAddress: string = '';
if (solToken) {
// let signer = provider.getSigner();
// let e = WrappedAssetFactory.connect(asset, provider);
// let addr = await signer.getAddress();
// let decimals = await e.decimals();
// let symbol = await e.symbol();
// TODO: checked if mint is wrapped mint from eth...
const accounts = userAccounts
.filter(a => a.info.mint.toBase58() === solToken.address)
.sort((a, b) => a.info.amount.toNumber() - b.info.amount.toNumber());
console.log(accounts);
if (!assetMeta || !currentAccount) {
setInfo(defaultCoinInfo);
return;
}
let info = {
address: currentAccount.pubkey.toBase58(),
name: solToken?.symbol || symbol,
balance: new BigNumber(currentAccount?.info.amount.toNumber() || 0),
allowance: new ethers.utils.BigNumber(0),
decimals: solToken?.decimals || decimals,
isWrapped: assetMeta.chain != ASSET_CHAIN.Solana,
chainID: assetMeta.chain,
assetAddress: assetMeta.address,
mint: solToken?.address || mintKeyAddress,
};
setInfo(info);
}
if (ethToken) {
if (chain === ASSET_CHAIN.Ethereum) {
if (!ethToken) {
setInfo(defaultCoinInfo);
return;
}
let signer = provider.getSigner();
let e = WrappedAssetFactory.connect(mintAddress, provider);
let addr = await signer.getAddress();
@ -158,7 +224,9 @@ export const useCurrencyLeg = (mintAddress: string) => {
}
if (chain === ASSET_CHAIN.Ethereum) {
info.balance = await e.balanceOf(addr);
info.balance = new BigNumber(
new ethers.utils.BigNumber(await e.balanceOf(addr)).toString(),
);
} else {
// TODO: get balance on other chains for assets that came from eth
@ -170,12 +238,9 @@ export const useCurrencyLeg = (mintAddress: string) => {
address: info.assetAddress,
chain: info.chainID,
});
console.log(mint.toBase58());
}
console.log(info);
//console.log({ info });
setInfo(info);
}
})();
@ -190,16 +255,13 @@ export const useCurrencyLeg = (mintAddress: string) => {
userAccounts,
]);
return useMemo(
() => ({
amount: amount,
setAmount: setAmount,
chain: chain,
setChain: setChain,
info,
}),
[amount, setAmount, chain, setChain],
);
return {
amount: amount,
setAmount: setAmount,
chain: chain,
setChain: setChain,
info,
};
};
export function TokenChainPairProvider({ children = null as any }) {
@ -223,7 +285,10 @@ export function TokenChainPairProvider({ children = null as any }) {
const setChainB = quote.setChain;
const tokens = useMemo(
() => [...ethTokens, ...filterModalSolTokens(solTokens)],
() => [
...filterModalEthTokens(ethTokens),
...filterModalSolTokens(solTokens),
],
[ethTokens, solTokens],
);
@ -249,7 +314,7 @@ export function TokenChainPairProvider({ children = null as any }) {
return;
}
let { defaultChain, defaultToken } = getDefaultTokens(
ethTokens,
tokens,
location.search,
);
if (!defaultToken || !defaultChain) {
@ -269,15 +334,7 @@ export function TokenChainPairProvider({ children = null as any }) {
);
// mintAddressA and mintAddressB are not included here to prevent infinite loop
// eslint-disable-next-line
}, [
location,
location.search,
location.pathname,
setMintAddress,
tokens,
setChainA,
setChainB,
]);
}, [location, location.search, location.pathname, tokens]);
const calculateDependent = useCallback(async () => {
if (mintAddress) {

View File

@ -7,13 +7,7 @@ import React, {
useMemo,
useState,
} from 'react';
// @ts-ignore
import { useWallet as useEthereumWallet } from 'use-wallet';
// @ts-ignore
import WalletConnectProvider from '@walletconnect/web3-provider';
// @ts-ignore
import Fortmatic from 'fortmatic';
import { useWallet, useLocalStorageState } from '@oyster/common';
import { WalletAdapter } from '@solana/wallet-base';
import { TokenList, TokenInfo } from '@uniswap/token-lists';
@ -230,6 +224,7 @@ export const EthereumProvider: FunctionComponent = ({ children }) => {
return (
<Button
key={provider.url}
size="large"
type={providerUrl === provider.url ? 'primary' : 'ghost'}
onClick={onClick}

View File

@ -4,22 +4,16 @@ import { Market, MARKETS, Orderbook, TOKEN_MINTS } from '@project-serum/serum';
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js';
import { useMemo } from 'react';
import {
contexts,
utils,
ParsedAccount,
KnownTokenMap,
EventEmitter,
} from '@oyster/common';
import { contexts, utils, EventEmitter } from '@oyster/common';
import { DexMarketParser } from './../models/dex';
import { MINT_TO_MARKET } from '../models/marketOverrides';
const { useConnectionConfig } = contexts.Connection;
const { convert, fromLamports, getTokenName, STABLE_COINS } = utils;
const { STABLE_COINS } = utils;
const { cache, getMultipleAccounts } = contexts.Accounts;
const INITAL_LIQUIDITY_DATE = new Date('2020-10-27');
export const BONFIDA_POOL_INTERVAL = 30 * 60_000; // 30 min
//const INITAL_LIQUIDITY_DATE = new Date('2020-10-27');
//export const BONFIDA_POOL_INTERVAL = 30 * 60_000; // 30 min
interface RecentPoolData {
pool_identifier: string;

View File

@ -14,7 +14,7 @@ export const useCorrectNetwork = () => {
if (chainId === 5) {
setHasCorrespondingNetworks(env === 'testnet');
} else if (chainId === 1) {
setHasCorrespondingNetworks(env === 'mainnet-beta');
setHasCorrespondingNetworks(env.includes('mainnet-beta'));
} else {
setHasCorrespondingNetworks(false);
}

View File

@ -1,30 +0,0 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import {
useConnection,
useConnectionConfig,
MintParser,
cache,
getMultipleAccounts,
ParsedAccount,
TokenAccountParser,
} from '@oyster/common';
import { WORMHOLE_PROGRAM_ID } from '../utils/ids';
import { ASSET_CHAIN } from '../utils/assets';
import { useEthereum } from '../contexts';
import { Connection, PublicKey } from '@solana/web3.js';
import { models } from '@oyster/common';
import { AccountInfo, MintInfo, TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { WrappedMetaLayout } from './../models/bridge';
import bs58 from 'bs58';
import {
COINGECKO_COIN_PRICE_API,
COINGECKO_POOL_INTERVAL,
useCoingecko,
} from '../contexts/coingecko';
export const useEthUserAccount = () => {
const [address, setAddress] = useState('');
// const { web3 } = useEthereum();
return address;
};

View File

@ -20,7 +20,7 @@ import {
bridgeAuthorityKey,
wrappedAssetMintKey,
WrappedMetaLayout,
} from './../models/bridge';
} from '@solana/bridge-sdk';
import bs58 from 'bs58';
import {
@ -157,7 +157,7 @@ const queryWrappedMetaAccounts = async (
if (asset.mint) {
asset.amount =
asset.mint?.info.supply.toNumber() /
Math.pow(10, asset.mint?.info.decimals) || 0;
Math.pow(10, asset.mint?.info.decimals || 0) ;
if (!asset.mint) {
throw new Error('missing mint');
@ -167,7 +167,11 @@ const queryWrappedMetaAccounts = async (
connection.onAccountChange(asset.mint?.pubkey, acc => {
cache.add(key, acc);
asset.mint = cache.get(key);
asset.amount = asset.mint?.info.supply.toNumber() || 0;
if (asset.mint) {
asset.amount =
asset.mint?.info.supply.toNumber() /
Math.pow(10, asset.mint?.info.decimals || 0);
}
setExternalAssets([...assets.values()]);
});
@ -275,7 +279,8 @@ export const useWormholeAccounts = () => {
})();
return () => {
connection.removeProgramAccountChangeListener(wormholeSubId);
if (wormholeSubId !== 0)
connection.removeProgramAccountChangeListener(wormholeSubId);
};
}, [connection, setExternalAssets]);
@ -330,7 +335,7 @@ export const useWormholeAccounts = () => {
if (ids.length === 0) {
return;
}
console.log('Querying Prices...');
const parameters = `?ids=${ids.join(',')}&vs_currencies=usd`;
const resp = await window.fetch(COINGECKO_COIN_PRICE_API + parameters);
const data = await resp.json();

View File

@ -0,0 +1,425 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import {
notify,
programIds,
TokenAccount,
useConnection,
useConnectionConfig,
useUserAccounts,
useWallet,
} from '@oyster/common';
import {
POSTVAA_INSTRUCTION,
TRANSFER_ASSETS_OUT_INSTRUCTION,
WORMHOLE_PROGRAM_ID,
} from '../utils/ids';
import { ASSET_CHAIN } from '../utils/assets';
import { useEthereum } from '../contexts';
import {
AccountInfo,
Connection,
ParsedAccountData,
PartiallyDecodedInstruction,
PublicKey,
RpcResponseAndContext,
} from '@solana/web3.js';
import {
bridgeAuthorityKey,
LockupStatus,
LockupWithStatus,
SolanaBridge,
TransferOutProposalLayout,
WormholeFactory,
} from '@solana/bridge-sdk';
import bs58 from 'bs58';
import {
COINGECKO_COIN_PRICE_API,
COINGECKO_POOL_INTERVAL,
useCoingecko,
} from '../contexts/coingecko';
import { BigNumber } from 'bignumber.js';
import { ethers } from 'ethers';
import { useBridge } from '../contexts/bridge';
import BN from 'bn.js';
import { keccak256 } from 'ethers/utils';
type WrappedTransferMeta = {
chain: number;
decimals: number;
address: string;
publicKey: PublicKey;
coinId?: string;
price?: number;
explorer?: any;
logo?: string;
symbol?: string;
amount: number;
value?: number | string;
txhash: string;
date: number; // timestamp
status?: string;
owner?: string;
lockup?: any;
vaa?: any;
};
const transferCache = new Map<string, WrappedTransferMeta>();
const queryOwnWrappedMetaTransactions = async (
authorityKey: PublicKey,
connection: Connection,
setTransfers: (arr: WrappedTransferMeta[]) => void,
provider: ethers.providers.Web3Provider,
tokenAccounts: TokenAccount[],
bridge?: SolanaBridge,
) => {
if (tokenAccounts && tokenAccounts.length > 0 && bridge) {
const transfers = new Map<string, WrappedTransferMeta>();
let wh = WormholeFactory.connect(programIds().wormhole.bridge, provider);
let lockups: LockupWithStatus[] = [];
for (const acc of tokenAccounts) {
const accLockups = await bridge.fetchTransferProposals(acc.pubkey);
lockups.push(
...accLockups.map(v => {
return {
status: LockupStatus.AWAITING_VAA,
...v,
};
}),
);
for (let lockup of lockups) {
if (lockup.vaaTime === undefined || lockup.vaaTime === 0) continue;
let signingData = lockup.vaa.slice(lockup.vaa[5] * 66 + 6);
for (let i = signingData.length; i > 0; i--) {
if (signingData[i] == 0xff) {
signingData = signingData.slice(0, i);
break;
}
}
let hash = keccak256(signingData);
let submissionStatus = await wh.consumedVAAs(hash);
lockup.status = submissionStatus
? LockupStatus.COMPLETED
: LockupStatus.UNCLAIMED_VAA;
}
}
for (const ls of lockups) {
const txhash = ls.lockupAddress.toBase58();
let assetAddress: string = '';
if (ls.assetChain !== ASSET_CHAIN.Solana) {
assetAddress = Buffer.from(ls.assetAddress.slice(12)).toString('hex');
} else {
assetAddress = new PublicKey(ls.assetAddress).toBase58();
}
const dec = new BigNumber(10).pow(new BigNumber(ls.assetDecimals));
const rawAmount = new BigNumber(ls.amount.toString());
const amount = rawAmount.div(dec).toNumber();
transfers.set(txhash, {
publicKey: ls.lockupAddress,
amount,
date: ls.vaaTime,
chain: ls.assetChain,
address: assetAddress,
decimals: 9,
txhash,
explorer: `https://explorer.solana.com/address/${txhash}`,
lockup: ls,
status:
ls.status === LockupStatus.UNCLAIMED_VAA
? 'Failed'
: ls.status === LockupStatus.AWAITING_VAA
? 'In Process'
: 'Completed',
});
}
setTransfers([...transfers.values()]);
}
};
const queryWrappedMetaTransactions = async (
authorityKey: PublicKey,
connection: Connection,
setTransfers: (arr: WrappedTransferMeta[]) => void,
provider: ethers.providers.Web3Provider,
bridge?: SolanaBridge,
) => {
const filters = [
{
dataSize: TransferOutProposalLayout.span,
},
];
let wh = WormholeFactory.connect(programIds().wormhole.bridge, provider);
const resp = await (connection as any)._rpcRequest('getProgramAccounts', [
WORMHOLE_PROGRAM_ID.toBase58(),
{
commitment: connection.commitment,
filters,
},
]);
const transfers = new Map<string, WrappedTransferMeta>();
resp.result
.map((acc: any) => ({
publicKey: new PublicKey(acc.pubkey),
account: {
data: bs58.decode(acc.account.data),
executable: acc.account.executable,
owner: new PublicKey(acc.account.owner),
lamports: acc.account.lamports,
},
}))
.map((acc: any) => {
if (acc.account.data.length === TransferOutProposalLayout.span) {
const metaTransfer = TransferOutProposalLayout.decode(acc.account.data);
let assetAddress: string = '';
if (metaTransfer.assetChain !== ASSET_CHAIN.Solana) {
assetAddress = Buffer.from(
metaTransfer.assetAddress.slice(12),
).toString('hex');
} else {
assetAddress = new PublicKey(metaTransfer.assetAddress).toBase58();
}
const dec = new BigNumber(10).pow(
new BigNumber(metaTransfer.assetDecimals),
);
const rawAmount = new BigNumber(
new BN(metaTransfer.amount, 2, 'le').toString(),
);
const amount = rawAmount.div(dec).toNumber();
const txhash = acc.publicKey.toBase58();
transfers.set(txhash, {
publicKey: acc.publicKey,
amount,
date: metaTransfer.vaaTime,
chain: metaTransfer.assetChain,
address: assetAddress,
decimals: 9,
txhash,
explorer: `https://explorer.solana.com/address/${txhash}`,
lockup: metaTransfer,
});
}
return null;
});
await Promise.all(
[...transfers.values()].map(async transfer => {
const cachedTransfer = transferCache.get(transfer.txhash);
if (cachedTransfer && cachedTransfer.status === 'Completed') {
transfer.vaa = cachedTransfer.vaa;
transfer.status = cachedTransfer.status;
transfer.owner = cachedTransfer.owner;
} else {
const resp = await (connection as any)._rpcRequest(
'getConfirmedSignaturesForAddress2',
[transfer.publicKey.toBase58()],
);
for (const sig of resp.result) {
const confirmedTx = await connection.getParsedConfirmedTransaction(
sig.signature,
'finalized',
);
if (!confirmedTx) continue;
const instructions = confirmedTx.transaction?.message?.instructions;
const filteredInstructions = instructions?.filter(ins => {
return ins.programId.toBase58() === WORMHOLE_PROGRAM_ID.toBase58();
});
if (filteredInstructions && filteredInstructions?.length > 0) {
for (const ins of filteredInstructions) {
const data = bs58.decode(
(ins as PartiallyDecodedInstruction).data,
);
if (data[0] === TRANSFER_ASSETS_OUT_INSTRUCTION) {
try {
transfer.owner = (ins as PartiallyDecodedInstruction).accounts[10].toBase58();
} catch {
// Catch no owner
transfer.owner = '';
}
}
if (
data[0] === POSTVAA_INSTRUCTION &&
confirmedTx.meta?.err == null &&
bridge
) {
const lockup = transfer.lockup;
let vaa = lockup.vaa;
for (let i = vaa.length; i > 0; i--) {
if (vaa[i] == 0xff) {
vaa = vaa.slice(0, i);
break;
}
}
try {
let signatures = await bridge.fetchSignatureStatus(
lockup.signatureAccount,
);
let sigData = Buffer.of(
...signatures.reduce((previousValue, currentValue) => {
previousValue.push(currentValue.index);
previousValue.push(...currentValue.signature);
return previousValue;
}, new Array<number>()),
);
vaa = Buffer.concat([
vaa.slice(0, 5),
Buffer.of(signatures.length),
sigData,
vaa.slice(6),
]);
try {
if (vaa?.length) {
const _ = await wh.parseAndVerifyVAA(vaa);
transfer.status = 'Failed';
transfer.vaa = vaa;
//TODO: handle vaa not posted
//console.log({ result });
} else {
transfer.status = 'Error';
transfer.vaa = vaa;
//TODO: handle empty data
//console.log({ vaa });
}
} catch (e) {
//console.log({ error: e });
transfer.vaa = vaa;
transfer.status = 'Completed';
transferCache.set(transfer.txhash, transfer);
}
} catch (e) {
transfer.status = 'Error';
transfer.vaa = vaa;
//TODO: handle error
}
}
}
}
}
}
}),
);
setTransfers([...transfers.values()]);
};
export const useWormholeTransactions = (tokenAccounts: TokenAccount[]) => {
const connection = useConnection();
const { tokenMap: ethTokens } = useEthereum();
const { tokenMap } = useConnectionConfig();
const { coinList } = useCoingecko();
const bridge = useBridge();
const [loading, setLoading] = useState<boolean>(true);
const [transfers, setTransfers] = useState<WrappedTransferMeta[]>([]);
const [amountInUSD, setAmountInUSD] = useState<number>(0);
useEffect(() => {
setLoading(true);
(async () => {
// authority -> query for token accounts to get locked assets
let authorityKey = await bridgeAuthorityKey(programIds().wormhole.pubkey);
if ((window as any).ethereum === undefined) {
notify({
message: 'Metamask Error',
description: 'Please install metamask wallet extension',
});
setLoading(false);
} else {
const provider = new ethers.providers.Web3Provider(
(window as any).ethereum,
);
// query wrapped assets that were imported to solana from other chains
queryOwnWrappedMetaTransactions(
authorityKey,
connection,
setTransfers,
provider,
tokenAccounts,
bridge,
).then(() => setLoading(false));
}
})();
}, [connection, setTransfers, tokenAccounts]);
const coingeckoTimer = useRef<number>(0);
const dataSourcePriceQuery = useCallback(async () => {
if (transfers.length === 0) return;
const ids = [
...new Set(
transfers
.map(transfer => {
let knownToken = tokenMap.get(transfer.address);
if (knownToken) {
transfer.logo = knownToken.logoURI;
transfer.symbol = knownToken.symbol;
}
let token = ethTokens.get(`0x${transfer.address || ''}`);
if (token) {
transfer.logo = token.logoURI;
transfer.symbol = token.symbol;
}
if (transfer.symbol) {
let coinInfo = coinList.get(transfer.symbol.toLowerCase());
if (coinInfo) {
transfer.coinId = coinInfo.id;
return coinInfo.id;
}
}
return '';
})
.filter(a => a?.length),
),
];
if (ids.length === 0) return;
console.log('Querying Prices...');
const parameters = `?ids=${ids.join(',')}&vs_currencies=usd`;
const resp = await window.fetch(COINGECKO_COIN_PRICE_API + parameters);
const usdByCoinId = await resp.json();
transfers.forEach(transfer => {
transfer.price = usdByCoinId[transfer.coinId as string]?.usd || 1;
transfer.value =
Math.round(transfer.amount * (transfer.price || 1) * 100) / 100;
});
setAmountInUSD(10);
coingeckoTimer.current = window.setTimeout(
() => dataSourcePriceQuery(),
COINGECKO_POOL_INTERVAL,
);
}, [transfers, setAmountInUSD, coinList]);
useEffect(() => {
if (transfers && coinList && !loading) {
dataSourcePriceQuery();
}
return () => {
window.clearTimeout(coingeckoTimer.current);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [transfers, coinList, loading]);
return {
loading,
transfers,
totalInUSD: amountInUSD,
};
};

View File

@ -1,18 +0,0 @@
// 40 - ExecutedVAA (claim)
export const NOP = 0;
/*
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ClaimedVAA {
/// hash of the vaa
pub hash: [u8; 32],
/// time the vaa was submitted
pub vaa_time: u32,
/// Is `true` if this structure has been initialized.
pub is_initialized: bool,
}
*/

View File

@ -1,11 +1,7 @@
import { HashRouter, Route, Switch } from 'react-router-dom';
import React from 'react';
import { contexts } from '@oyster/common';
import {
MarketProvider,
TokenPairProvider,
EthereumProvider,
} from './contexts';
import { MarketProvider, EthereumProvider } from './contexts';
import { AppLayout } from './components/Layout';
import {
@ -45,7 +41,7 @@ export function Routes() {
component={() => <HomeView />}
/>
<Route path="/move" children={<TransferView />} />
<Route path="/faq" children={<FaqView />} />
{/*<Route path="/faq" children={<FaqView />} />*/}
<Route
path="/proof-of-assets"
children={<ProofOfAssetsView />}

View File

@ -49,6 +49,20 @@ export const chainToName = (chain?: ASSET_CHAIN) => {
return CHAIN_NAME[chain || ASSET_CHAIN.Ethereum];
};
const EXCLUDED_COMMON_TOKENS = ['usdt', 'usdc'];
const EXCLUDED_SPL_TOKENS = ['sol', 'srm', ...EXCLUDED_COMMON_TOKENS];
export const filterModalSolTokens = (tokens: TokenInfo[]) => {
return tokens;
return tokens.filter(
token =>
EXCLUDED_SPL_TOKENS.indexOf(token.symbol.toLowerCase()) < 0 &&
!token.name.includes('(Sollet)'),
);
};
const EXCLUDED_ETH_TOKENS = [...EXCLUDED_COMMON_TOKENS];
export const filterModalEthTokens = (tokens: TokenInfo[]) => {
return tokens.filter(
token => EXCLUDED_ETH_TOKENS.indexOf(token.symbol.toLowerCase()) < 0,
);
};

View File

@ -1,5 +1,37 @@
import { PublicKey } from '@solana/web3.js';
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
export const WORMHOLE_PROGRAM_ID = new PublicKey(
'WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC',
);
export const TRANSFER_ASSETS_OUT_INSTRUCTION: number = 1;
export const POSTVAA_INSTRUCTION: number = 2;
const INSTRUCTION_LOOKUP: { [key: number]: string } = {
0: 'Initialize Bridge',
[TRANSFER_ASSETS_OUT_INSTRUCTION]: 'Transfer Assets Out',
[POSTVAA_INSTRUCTION]: 'Post VAA',
3: 'Evict Transfer Proposal',
4: 'Evict Claimed VAA',
5: 'Poke Proposal',
6: 'Verify Signatures',
7: 'Create Wrapped Asset',
};
export function isWormholeInstruction(
instruction: TransactionInstruction,
): boolean {
return WORMHOLE_PROGRAM_ID.toBase58() === instruction.programId.toBase58();
}
export function parsWormholeInstructionTitle(
instruction: TransactionInstruction,
): string {
const code = instruction.data[0];
if (!(code in INSTRUCTION_LOOKUP)) {
throw new Error(`Unrecognized Wormhole instruction code: ${code}`);
}
return INSTRUCTION_LOOKUP[code];
}

View File

@ -0,0 +1,28 @@
.description-container {
.ant-row {
align-items: center;
margin-bottom: 50px;
}
.main-logo {
.logo-title {
margin-bottom: 0;
font-size: 35px;
letter-spacing: 0;
}
}
}
.q-title {
font-size: 32px;
line-height: 160%;
max-width: 80%;
margin: auto;
}
.q-description {
font-size: 20px;
line-height: 160%;
max-width: 80%;
margin: auto;
margin-top: 20px;
text-align: justify;
}

View File

@ -1,4 +1,5 @@
import React from 'react';
import { Row, Col, Button } from 'antd';
import './index.less';
@ -6,7 +7,47 @@ export const HelpView = () => {
return (
<div
className="flexColumn transfer-bg"
style={{ flex: 1, minHeight: '90vh' }}
></div>
style={{ flex: 1, minHeight: '90vh', paddingTop: '100px' }}
>
<div className={'description-container'}>
<Row>
<Col xs={24} sm={12}>
<div className={'q-title'}>How does Wormhole Work?</div>
<p className={'q-description'}>
Wormhole allows existing projects, platforms, and communities to
move tokenized assets seamlessly across blockchains to benefit
from Solanas high speed and low cost. Wormhole does this by
wrapping ERC-20 tokens, which are then usable in Solanas low-cost
defi ecosystem.
</p>
</Col>
<Col xs={24} sm={12}>
<div className={'main-logo'}>
<img src={'/help/overview.svg'} />
</div>
</Col>
</Row>
<Row>
<Col xs={24} sm={12}>
<Button
className="action-button"
onClick={() =>
window.open('https://github.com/certusone/wormhole', '_blank')
}
>
View the Code
</Button>
</Col>
<Col xs={24} sm={12}>
<div className={'q-title'}>
How can I integrate Wormhole into my wallet or dapp?
</div>
<p className={'q-description'}>
Wormhole is an open-source project accessible to all.
</p>
</Col>
</Row>
</div>
</div>
);
};

View File

@ -25,6 +25,18 @@ section.ant-layout {
width: 25px;
height: 21px;
}
.logo-title {
/* SOLANA <-> ETHEREUM BRIDGE */
background: linear-gradient(90deg, #DC1FFF 11.29%, #4DA9FF 51.42%, #00CC82 93.23%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-size: 16px;
line-height: 160%;
text-align: center;
letter-spacing: 0.8em;
text-transform: uppercase;
margin-bottom: 90px;
}
.home-subtitle {
font-size: 32px;
@ -39,6 +51,7 @@ section.ant-layout {
.home-container {
width: 100%;
background-color: #06101a;
#how-it-works-container {
display: flex;
flex-direction: column;
@ -54,9 +67,9 @@ section.ant-layout {
.home-description {
display: flex;
flex-direction: row;
justify-content: space-evenly;
max-width: 90%;
width: 90%;
margin: auto;
justify-content: space-evenly;
.home-description-item {
padding: 0 30px 0 30px;
@ -117,7 +130,7 @@ section.ant-layout {
font-size: 16px;
line-height: 160%;
width: auto;
margin: 3px 20px 30px 20px;
margin: 3px 20px 40px 20px;
}
& > div:first-child {
margin-bottom: 120px;
@ -141,7 +154,6 @@ footer.ant-layout-footer {
.wormhole-bg {
background-image: url('/home/background.svg');
background-size: 100% auto;
background-color: #06101a;
background-repeat: no-repeat;
}

View File

@ -1,117 +1,15 @@
import { Table, Col, Row, Statistic, Button } from 'antd';
import anime from 'animejs';
import React, { useMemo } from 'react';
import { GUTTER } from '../../constants';
import { formatNumber, formatUSD, shortenAddress } from '@oyster/common';
import React from 'react';
import { formatUSD, shortenAddress } from '@oyster/common';
import './itemStyle.less';
import './index.less';
import { Link } from 'react-router-dom';
import { useWormholeAccounts } from '../../hooks/useWormholeAccounts';
import { TokenDisplay } from '../../components/TokenDisplay';
import { toChainSymbol } from '../../contexts/chainPair';
import { AssetsTable } from '../../components/AssetsTable';
export const HomeView = () => {
const {
loading: loadingLockedAccounts,
externalAssets,
totalInUSD,
} = useWormholeAccounts();
const columns = [
{
title: 'Symbol',
dataIndex: 'symbol',
key: 'symbol',
render(text: string, record: any) {
return {
props: {
style: {},
},
children: (
<Link
to={`/move?from=${toChainSymbol(record.chain)}&token=${
record.symbol
}`}
>
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
{record.logo && (
<TokenDisplay logo={record.logo} chain={record.chain} />
)}{' '}
{record.symbol}
</span>
</Link>
),
};
},
},
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Amount',
dataIndex: 'amount',
key: 'amount',
},
{
title: 'Amount ($)',
dataIndex: 'amountInUSD',
key: 'amountInUSD',
},
{
title: 'Price',
dataIndex: 'price',
width: 100,
key: 'price',
render(text: string, record: any) {
return {
props: {
style: { textAlign: 'right' },
},
children: record.price ? formatUSD.format(record.price) : '--',
};
},
},
{
title: 'Asset Address',
dataIndex: 'address',
key: 'address',
render(text: string, record: any) {
return {
props: {
style: {},
},
children: (
<a href={record.explorer} target="_blank" rel="noopener noreferrer">
{shortenAddress(text, 6)}
</a>
),
};
},
},
{
title: 'Wrapped Address',
dataIndex: 'mintKey',
key: 'mintKey',
render(text: string, record: any) {
return {
props: {
style: {},
},
children: (
<a
href={record.wrappedExplorer}
target="_blank"
rel="noopener noreferrer"
>
{shortenAddress(text, 6)}
</a>
),
};
},
},
];
const handleDownArrow = () => {
const scrollTo = document.getElementById('how-it-works-container');
const scrollElement =
@ -127,14 +25,18 @@ export const HomeView = () => {
};
return (
<>
<div className="flexColumn home-container wormhole-bg">
<div className={'justify-bottom-container'}>
<div className="flexColumn home-container">
<div className={'justify-bottom-container wormhole-bg'}>
<div className={'main-logo'}>
<div className={'logo-title'}>
{' '}
SOLANA &lt;-&gt; ETHEREUM BRIDGE
</div>
<img src={'/home/main-logo.svg'} />
</div>
<div>
A decentralized and bi-directional bridge for
<br /> ERC-20 and SPL tokens
Easily move any tokens between Ethereum and Solana <br /> with
Wormholes bi-directional bridge
</div>
<div className={'grow-effect'}>
<Link to="/move">
@ -158,51 +60,25 @@ export const HomeView = () => {
<img src={'/home/icons/bridge-direction.svg'} />
</div>
<div className={'description-title'}>Bridge in any direction</div>
<div className={'description-text'}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis
nisi at praesent sed sollicitudin ullamcorper malesuada in.
Molestie sed morbi vitae in amet ultrices.
</div>
<div className={'description-text'}></div>
</div>
<div className={'home-description-item'}>
<div className={'description-icon'}>
<img src={'/home/icons/sd-card.svg'} />
</div>
<div className={'description-title'}>Staking & Validation</div>
<div className={'description-text'}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis
nisi at praesent sed sollicitudin ullamcorper malesuada in.
Molestie sed morbi vitae in amet ultrices.
</div>
<div className={'description-text'}></div>
</div>
<div className={'home-description-item'}>
<div className={'description-icon'}>
<img src={'/home/icons/layers.svg'} />
</div>
<div className={'description-title'}>Layers and Capabilities</div>
<div className={'description-text'}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis
nisi at praesent sed sollicitudin ullamcorper malesuada in.
Molestie sed morbi vitae in amet ultrices.
</div>
<div className={'description-text'}></div>
</div>
</div>
</div>
<div id={'recent-tx-container'}>
<div className={'home-subtitle'}>Recent Transactions</div>
<div className={'description-text'} style={{ marginBottom: '70px' }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</div>
<Table
scroll={{
scrollToFirstRowOnChange: false,
x: 900,
}}
dataSource={externalAssets.filter(a => a.name)}
columns={columns}
loading={loadingLockedAccounts}
/>
</div>
<AssetsTable />
</div>
</>
);

View File

@ -0,0 +1,3 @@
div:not(.home-container) > #recent-tx-container {
margin-top: 150px;
}

View File

@ -1,12 +1,15 @@
import React from 'react';
import './index.less';
import { AssetsTable } from '../../components/AssetsTable';
export const ProofOfAssetsView = () => {
return (
<div
className="flexColumn transfer-bg"
style={{ flex: 1, minHeight: '90vh' }}
></div>
>
<AssetsTable />
</div>
);
};

View File

@ -1,6 +1,5 @@
import React from 'react';
import './index.less';
import { Card } from 'antd';
import { Transfer } from '../../components/Transfer';
export const TransferView = () => {

View File

@ -1,6 +1,5 @@
import EventEmitter from 'eventemitter3';
import { PublicKey, Transaction } from '@solana/web3.js';
import { notify } from '@oyster/common';
import { WalletAdapter } from '@solana/wallet-base';
import { ethers } from 'ethers';
import WalletConnectProvider from '@walletconnect/web3-provider';

View File

@ -1,6 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"target": "es2019",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
@ -9,14 +10,13 @@
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"downlevelIteration": true,
"noEmit": true,
"jsx": "react",
"typeRoots": ["types", "../../types", "../../node_modules/@types"]
"typeRoots": ["types", "../../types", "../../node_modules/@types"],
"moduleResolution": "node"
},
"include": ["src"]
}

View File

@ -25,12 +25,13 @@
"prepare": "run-s clean build"
},
"dependencies": {
"@solana/wallet-base": "0.0.1",
"@project-serum/serum": "^0.13.11",
"@project-serum/sol-wallet-adapter": "^0.1.4",
"@solana/spl-token": "0.0.13",
"@solana/spl-token-swap": "0.1.0",
"@solana/web3.js": "^0.86.2",
"@solana/wallet-base": "0.0.1",
"@solana/wallet-ledger": "0.0.1",
"@solana/web3.js": "^1.5.0",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
@ -39,7 +40,9 @@
"@types/react-router-dom": "^5.1.6",
"@welldone-software/why-did-you-render": "^6.0.5",
"antd": "^4.6.6",
"bignumber.js": "^9.0.1",
"bn.js": "^5.1.3",
"borsh": "^0.3.1",
"bs58": "^4.0.1",
"buffer-layout": "^1.2.0",
"eventemitter3": "^4.0.7",
@ -57,9 +60,9 @@
"@types/jest": "^24.9.1",
"@types/node": "^12.12.62",
"arweave-deploy": "^1.9.1",
"less-watch-compiler": "v1.14.6",
"less": "4.1.1",
"gh-pages": "^3.1.0",
"less": "4.1.1",
"less-watch-compiler": "v1.14.6",
"prettier": "^2.1.2"
},
"files": [

View File

@ -0,0 +1,630 @@
import {
AccountInfo,
PublicKey,
SystemProgram,
SYSVAR_CLOCK_PUBKEY,
SYSVAR_RENT_PUBKEY,
TransactionInstruction,
} from '@solana/web3.js';
import { programIds } from '../utils/ids';
import { deserializeBorsh } from './../utils/borsh';
import { serialize } from 'borsh';
import BN from 'bn.js';
import { AccountParser, cache } from '../contexts';
export const AUCTION_PREFIX = 'auction';
export const METADATA = 'metadata';
export enum AuctionState {
Created = 0,
Started,
Ended,
}
export enum BidStateType {
EnglishAuction = 0,
OpenEdition = 1,
}
export class Bid {
key: PublicKey;
amount: BN;
constructor(args: { key: PublicKey; amount: BN }) {
this.key = args.key;
this.amount = args.amount;
}
}
export class BidState {
type: BidStateType;
bids: Bid[];
max: BN;
public getWinnerIndex(bidder: PublicKey): number | null {
if (!this.bids) return null;
console.log(
'bids',
this.bids.map(b => b.key.toBase58()),
bidder.toBase58(),
);
const index = this.bids.findIndex(
b => b.key.toBase58() == bidder.toBase58(),
);
if (index != -1) return index;
else return null;
}
constructor(args: { type: BidStateType; bids: Bid[]; max: BN }) {
this.type = args.type;
this.bids = args.bids;
this.max = args.max;
}
}
export const AuctionParser: AccountParser = (
pubkey: PublicKey,
account: AccountInfo<Buffer>,
) => ({
pubkey,
account,
info: decodeAuction(account.data),
});
export const decodeAuction = (buffer: Buffer) => {
return deserializeBorsh(AUCTION_SCHEMA, AuctionData, buffer) as AuctionData;
};
export const BidderPotParser: AccountParser = (
pubkey: PublicKey,
account: AccountInfo<Buffer>,
) => ({
pubkey,
account,
info: decodeBidderPot(account.data),
});
export const decodeBidderPot = (buffer: Buffer) => {
return deserializeBorsh(AUCTION_SCHEMA, BidderPot, buffer) as BidderPot;
};
export const BidderMetadataParser: AccountParser = (
pubkey: PublicKey,
account: AccountInfo<Buffer>,
) => ({
pubkey,
account,
info: decodeBidderMetadata(account.data),
});
export const decodeBidderMetadata = (buffer: Buffer) => {
return deserializeBorsh(
AUCTION_SCHEMA,
BidderMetadata,
buffer,
) as BidderMetadata;
};
export const BASE_AUCTION_DATA_SIZE = 32 + 32 + 32 + 8 + 8 + 1 + 9 + 9 + 9 + 9;
export class AuctionData {
/// Pubkey of the authority with permission to modify this auction.
authority: PublicKey;
/// Pubkey of the resource being bid on.
resource: PublicKey;
/// Token mint for the SPL token being used to bid
tokenMint: PublicKey;
/// The time the last bid was placed, used to keep track of auction timing.
lastBid: BN | null;
/// Slot time the auction was officially ended by.
endedAt: BN | null;
/// End time is the cut-off point that the auction is forced to end by.
endAuctionAt: BN | null;
/// Gap time is the amount of time in slots after the previous bid at which the auction ends.
auctionGap: BN | null;
/// The state the auction is in, whether it has started or ended.
state: AuctionState;
/// Auction Bids, each user may have one bid open at a time.
bidState: BidState;
/// Used for precalculation on the front end, not a backend key
auctionManagerKey?: PublicKey;
/// Used for precalculation on the front end, not a backend key
bidRedemptionKey?: PublicKey;
constructor(args: {
authority: PublicKey;
resource: PublicKey;
tokenMint: PublicKey;
lastBid: BN | null;
endedAt: BN | null;
endAuctionAt: BN | null;
auctionGap: BN | null;
state: AuctionState;
bidState: BidState;
}) {
this.authority = args.authority;
this.resource = args.resource;
this.tokenMint = args.tokenMint;
this.lastBid = args.lastBid;
this.endedAt = args.endedAt;
this.endAuctionAt = args.endAuctionAt;
this.auctionGap = args.auctionGap;
this.state = args.state;
this.bidState = args.bidState;
}
}
export const BIDDER_METADATA_LEN = 32 + 32 + 8 + 8 + 1;
export class BidderMetadata {
// Relationship with the bidder who's metadata this covers.
bidderPubkey: PublicKey;
// Relationship with the auction this bid was placed on.
auctionPubkey: PublicKey;
// Amount that the user bid.
lastBid: BN;
// Tracks the last time this user bid.
lastBidTimestamp: BN;
// Whether the last bid the user made was cancelled. This should also be enough to know if the
// user is a winner, as if cancelled it implies previous bids were also cancelled.
cancelled: boolean;
constructor(args: {
bidderPubkey: PublicKey;
auctionPubkey: PublicKey;
lastBid: BN;
lastBidTimestamp: BN;
cancelled: boolean;
}) {
this.bidderPubkey = args.bidderPubkey;
this.auctionPubkey = args.auctionPubkey;
this.lastBid = args.lastBid;
this.lastBidTimestamp = args.lastBidTimestamp;
this.cancelled = args.cancelled;
}
}
export const BIDDER_POT_LEN = 32 + 32 + 32;
export class BidderPot {
/// Points at actual pot that is a token account
bidderPot: PublicKey;
bidderAct: PublicKey;
auctionAct: PublicKey;
constructor(args: {
bidderPot: PublicKey;
bidderAct: PublicKey;
auctionAct: PublicKey;
}) {
this.bidderPot = args.bidderPot;
this.bidderAct = args.bidderAct;
this.auctionAct = args.auctionAct;
}
}
export enum WinnerLimitType {
Unlimited = 0,
Capped = 1,
}
export class WinnerLimit {
type: WinnerLimitType;
usize: BN;
constructor(args: { type: WinnerLimitType; usize: BN }) {
this.type = args.type;
this.usize = args.usize;
}
}
class CreateAuctionArgs {
instruction: number = 0;
/// How many winners are allowed for this auction. See AuctionData.
winners: WinnerLimit;
/// End time is the cut-off point that the auction is forced to end by. See AuctionData.
endAuctionAt: BN | null;
/// Gap time is how much time after the previous bid where the auction ends. See AuctionData.
auctionGap: BN | null;
/// Token mint for the SPL token used for bidding.
tokenMint: PublicKey;
/// Authority
authority: PublicKey;
/// The resource being auctioned. See AuctionData.
resource: PublicKey;
constructor(args: {
winners: WinnerLimit;
endAuctionAt: BN | null;
auctionGap: BN | null;
tokenMint: PublicKey;
authority: PublicKey;
resource: PublicKey;
}) {
this.winners = args.winners;
this.endAuctionAt = args.endAuctionAt;
this.auctionGap = args.auctionGap;
this.tokenMint = args.tokenMint;
this.authority = args.authority;
this.resource = args.resource;
}
}
class StartAuctionArgs {
instruction: number = 1;
resource: PublicKey;
constructor(args: { resource: PublicKey }) {
this.resource = args.resource;
}
}
class PlaceBidArgs {
instruction: number = 2;
resource: PublicKey;
amount: BN;
constructor(args: { resource: PublicKey; amount: BN }) {
this.resource = args.resource;
this.amount = args.amount;
}
}
export const AUCTION_SCHEMA = new Map<any, any>([
[
CreateAuctionArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['winners', WinnerLimit],
['endAuctionAt', { kind: 'option', type: 'u64' }],
['auctionGap', { kind: 'option', type: 'u64' }],
['tokenMint', 'pubkey'],
['authority', 'pubkey'],
['resource', 'pubkey'],
],
},
],
[
WinnerLimit,
{
kind: 'struct',
fields: [
['type', 'u8'],
['usize', 'u64'],
],
},
],
[
StartAuctionArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['resource', 'pubkey'],
],
},
],
[
PlaceBidArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['amount', 'u64'],
['resource', 'pubkey'],
],
},
],
[
AuctionData,
{
kind: 'struct',
fields: [
['authority', 'pubkey'],
['resource', 'pubkey'],
['tokenMint', 'pubkey'],
['lastBid', { kind: 'option', type: 'u64' }],
['endedAt', { kind: 'option', type: 'u64' }],
['endAuctionAt', { kind: 'option', type: 'u64' }],
['auctionGap', { kind: 'option', type: 'u64' }],
['state', 'u8'],
['bidState', BidState],
],
},
],
[
BidState,
{
kind: 'struct',
fields: [
['type', 'u8'],
['bids', [Bid]],
['max', 'u64'],
],
},
],
[
Bid,
{
kind: 'struct',
fields: [
['key', 'pubkey'],
['amount', 'u64'],
],
},
],
[
BidderMetadata,
{
kind: 'struct',
fields: [
['bidderPubkey', 'pubkey'],
['auctionPubkey', 'pubkey'],
['lastBid', 'u64'],
['lastBidTimestamp', 'u64'],
['cancelled', 'u8'],
],
},
],
[
BidderPot,
{
kind: 'struct',
fields: [
['bidderPot', 'pubkey'],
['bidderAct', 'pubkey'],
['auctionAct', 'pubkey'],
],
},
],
]);
export const decodeAuctionData = (buffer: Buffer) => {
return deserializeBorsh(AUCTION_SCHEMA, AuctionData, buffer) as AuctionData;
};
export async function createAuction(
winners: WinnerLimit,
resource: PublicKey,
endAuctionAt: BN | null,
auctionGap: BN | null,
tokenMint: PublicKey,
authority: PublicKey,
creator: PublicKey,
instructions: TransactionInstruction[],
) {
const auctionProgramId = programIds().auction;
const data = Buffer.from(
serialize(
AUCTION_SCHEMA,
new CreateAuctionArgs({
winners,
resource,
endAuctionAt,
auctionGap,
tokenMint,
authority,
}),
),
);
const auctionKey: PublicKey = (
await PublicKey.findProgramAddress(
[
Buffer.from(AUCTION_PREFIX),
auctionProgramId.toBuffer(),
resource.toBuffer(),
],
auctionProgramId,
)
)[0];
const keys = [
{
pubkey: creator,
isSigner: true,
isWritable: true,
},
{
pubkey: auctionKey,
isSigner: false,
isWritable: true,
},
{
pubkey: SYSVAR_RENT_PUBKEY,
isSigner: false,
isWritable: false,
},
{
pubkey: SystemProgram.programId,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: auctionProgramId,
data: data,
}),
);
}
export async function startAuction(
resource: PublicKey,
creator: PublicKey,
instructions: TransactionInstruction[],
) {
const auctionProgramId = programIds().auction;
const data = Buffer.from(
serialize(
AUCTION_SCHEMA,
new StartAuctionArgs({
resource,
}),
),
);
const auctionKey: PublicKey = (
await PublicKey.findProgramAddress(
[
Buffer.from(AUCTION_PREFIX),
auctionProgramId.toBuffer(),
resource.toBuffer(),
],
auctionProgramId,
)
)[0];
const keys = [
{
pubkey: creator,
isSigner: false,
isWritable: true,
},
{
pubkey: auctionKey,
isSigner: false,
isWritable: true,
},
{
pubkey: SYSVAR_CLOCK_PUBKEY,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: auctionProgramId,
data: data,
}),
);
}
export async function placeBid(
bidderPubkey: PublicKey,
bidderPotTokenPubkey: PublicKey,
tokenMintPubkey: PublicKey,
transferAuthority: PublicKey,
payer: PublicKey,
resource: PublicKey,
amount: BN,
instructions: TransactionInstruction[],
) {
const auctionProgramId = programIds().auction;
const data = Buffer.from(
serialize(
AUCTION_SCHEMA,
new PlaceBidArgs({
resource,
amount,
}),
),
);
const auctionKey: PublicKey = (
await PublicKey.findProgramAddress(
[
Buffer.from(AUCTION_PREFIX),
auctionProgramId.toBuffer(),
resource.toBuffer(),
],
auctionProgramId,
)
)[0];
const bidderPotKey: PublicKey = (
await PublicKey.findProgramAddress(
[
Buffer.from(AUCTION_PREFIX),
auctionProgramId.toBuffer(),
auctionKey.toBuffer(),
bidderPubkey.toBuffer(),
],
auctionProgramId,
)
)[0];
const bidderMetaKey: PublicKey = (
await PublicKey.findProgramAddress(
[
Buffer.from(AUCTION_PREFIX),
auctionProgramId.toBuffer(),
auctionKey.toBuffer(),
bidderPubkey.toBuffer(),
Buffer.from('metadata'),
],
auctionProgramId,
)
)[0];
const keys = [
{
pubkey: bidderPubkey,
isSigner: false,
isWritable: true,
},
{
pubkey: bidderPotKey,
isSigner: false,
isWritable: true,
},
{
pubkey: bidderPotTokenPubkey,
isSigner: false,
isWritable: true,
},
{
pubkey: bidderMetaKey,
isSigner: false,
isWritable: true,
},
{
pubkey: auctionKey,
isSigner: false,
isWritable: true,
},
{
pubkey: tokenMintPubkey,
isSigner: false,
isWritable: true,
},
{
pubkey: transferAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: payer,
isSigner: true,
isWritable: false,
},
{
pubkey: SYSVAR_CLOCK_PUBKEY,
isSigner: false,
isWritable: false,
},
{
pubkey: SYSVAR_RENT_PUBKEY,
isSigner: false,
isWritable: false,
},
{
pubkey: SystemProgram.programId,
isSigner: false,
isWritable: false,
},
{
pubkey: programIds().token,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: auctionProgramId,
data: data,
}),
);
}

View File

@ -1 +1,4 @@
export * from './account';
export * from './metadata';
export * from './vault';
export * from './auction';

View File

@ -0,0 +1,780 @@
import {
PublicKey,
SystemProgram,
SYSVAR_RENT_PUBKEY,
TransactionInstruction,
} from '@solana/web3.js';
import { programIds } from '../utils/ids';
import { deserializeBorsh } from './../utils/borsh';
import { serialize } from 'borsh';
import BN from 'bn.js';
import { PublicKeyInput } from 'node:crypto';
import { ParsedAccount } from '..';
export const METADATA_PREFIX = 'metadata';
export const EDITION = 'edition';
export const MAX_NAME_LENGTH = 32;
export const MAX_SYMBOL_LENGTH = 10;
export const MAX_URI_LENGTH = 200;
export const MAX_METADATA_LEN =
1 + 32 + MAX_NAME_LENGTH + MAX_SYMBOL_LENGTH + MAX_URI_LENGTH + 200;
export const MAX_NAME_SYMBOL_LEN = 1 + 32 + 8;
export const MAX_MASTER_EDITION_KEN = 1 + 9 + 8 + 32;
export enum MetadataKey {
MetadataV1 = 0,
NameSymbolTupleV1 = 1,
EditionV1 = 2,
MasterEditionV1 = 3,
}
export enum MetadataCategory {
Audio = 'audio',
Video = 'video',
Image = 'image',
}
export interface IMetadataExtension {
name: string;
symbol: string;
description: string;
// preview image
image: string;
// stores link to item on meta
externalUrl: string;
royalty: number;
files?: File[];
category: MetadataCategory;
}
export class MasterEdition {
key: MetadataKey;
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: MetadataKey;
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 = MetadataKey.MasterEditionV1;
this.supply = args.supply;
this.maxSupply = args.maxSupply;
this.masterMint = args.masterMint;
}
}
export class Edition {
key: MetadataKey;
/// Points at MasterEdition struct
parent: PublicKey;
/// Starting at 0 for master record, this is incremented for each edition minted.
edition: BN;
constructor(args: { key: MetadataKey; parent: PublicKey; edition: BN }) {
this.key = MetadataKey.EditionV1;
this.parent = args.parent;
this.edition = args.edition;
}
}
export class Metadata {
key: MetadataKey;
nonUniqueSpecificUpdateAuthority?: PublicKey;
mint: PublicKey;
name: string;
symbol: string;
uri: string;
extended?: IMetadataExtension;
masterEdition?: PublicKey;
edition?: PublicKey;
nameSymbolTuple?: PublicKey;
constructor(args: {
nonUniqueSpecificUpdateAuthority?: PublicKey;
mint: PublicKey;
name: string;
symbol: string;
uri: string;
}) {
this.key = MetadataKey.MetadataV1;
this.nonUniqueSpecificUpdateAuthority =
args.nonUniqueSpecificUpdateAuthority;
this.mint = args.mint;
this.name = args.name;
this.symbol = args.symbol;
this.uri = args.uri;
}
}
export class NameSymbolTuple {
key: MetadataKey;
updateAuthority: PublicKey;
metadata: PublicKey;
constructor(args: { updateAuthority: Buffer; metadata: Buffer }) {
this.key = MetadataKey.NameSymbolTupleV1;
this.updateAuthority = new PublicKey(args.updateAuthority);
this.metadata = new PublicKey(args.metadata);
}
}
class CreateMetadataArgs {
instruction: number = 0;
allowDuplicates: boolean = false;
name: string;
symbol: string;
uri: string;
constructor(args: {
name: string;
symbol: string;
uri: string;
allowDuplicates?: boolean;
}) {
this.name = args.name;
this.symbol = args.symbol;
this.uri = args.uri;
this.allowDuplicates = !!args.allowDuplicates;
}
}
class UpdateMetadataArgs {
instruction: number = 1;
uri: string;
// Not used by this app, just required for instruction
nonUniqueSpecificUpdateAuthority: PublicKey | null;
constructor(args: {
uri: string;
nonUniqueSpecificUpdateAuthority?: string;
}) {
this.uri = args.uri;
this.nonUniqueSpecificUpdateAuthority = args.nonUniqueSpecificUpdateAuthority
? new PublicKey(args.nonUniqueSpecificUpdateAuthority)
: null;
}
}
class TransferUpdateAuthorityArgs {
instruction: number = 2;
constructor() {}
}
class CreateMasterEditionArgs {
instruction: number = 3;
maxSupply: BN | null;
constructor(args: { maxSupply: BN | null }) {
this.maxSupply = args.maxSupply;
}
}
export const METADATA_SCHEMA = new Map<any, any>([
[
CreateMetadataArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['allowDuplicates', 'u8'],
['name', 'string'],
['symbol', 'string'],
['uri', 'string'],
],
},
],
[
UpdateMetadataArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['uri', 'string'],
[
'nonUniqueSpecificUpdateAuthority',
{ kind: 'option', type: 'pubkey' },
],
],
},
],
[
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', 'pubkey'],
],
},
],
[
Edition,
{
kind: 'struct',
fields: [
['key', 'u8'],
['parent', 'pubkey'],
['edition', 'u64'],
],
},
],
[
Metadata,
{
kind: 'struct',
fields: [
['key', 'u8'],
[
'nonUniqueSpecificUpdateAuthority',
{ kind: 'option', type: 'pubkey' },
],
['mint', 'pubkey'],
['name', 'string'],
['symbol', 'string'],
['uri', 'string'],
],
},
],
[
NameSymbolTuple,
{
kind: 'struct',
fields: [
['key', 'u8'],
['updateAuthority', 'pubkey'],
['metadata', 'pubkey'],
],
},
],
]);
export const decodeMetadata = async (buffer: Buffer): Promise<Metadata> => {
const metadata = deserializeBorsh(
METADATA_SCHEMA,
Metadata,
buffer,
) as Metadata;
metadata.nameSymbolTuple = await getNameSymbol(metadata);
metadata.edition = await getEdition(metadata.mint);
metadata.masterEdition = await getEdition(metadata.mint);
return metadata;
};
export const decodeEdition = (buffer: Buffer) => {
return deserializeBorsh(METADATA_SCHEMA, Edition, buffer) as Edition;
};
export const decodeMasterEdition = (buffer: Buffer) => {
return deserializeBorsh(
METADATA_SCHEMA,
MasterEdition,
buffer,
) as MasterEdition;
};
export const decodeNameSymbolTuple = (buffer: Buffer) => {
return deserializeBorsh(
METADATA_SCHEMA,
NameSymbolTuple,
buffer,
) as NameSymbolTuple;
};
export async function transferUpdateAuthority(
account: PublicKey,
currentUpdateAuthority: PublicKey,
newUpdateAuthority: PublicKey,
instructions: TransactionInstruction[],
) {
const metadataProgramId = programIds().metadata;
const data = Buffer.from(
serialize(METADATA_SCHEMA, new TransferUpdateAuthorityArgs()),
);
const keys = [
{
pubkey: account,
isSigner: false,
isWritable: true,
},
{
pubkey: currentUpdateAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: newUpdateAuthority,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: metadataProgramId,
data: data,
}),
);
}
export async function updateMetadata(
symbol: string,
name: string,
uri: string,
newNonUniqueSpecificUpdateAuthority: string | undefined,
mintKey: PublicKey,
updateAuthority: PublicKey,
instructions: TransactionInstruction[],
metadataAccount?: PublicKey,
nameSymbolAccount?: PublicKey,
) {
const metadataProgramId = programIds().metadata;
metadataAccount =
metadataAccount ||
(
await PublicKey.findProgramAddress(
[
Buffer.from('metadata'),
metadataProgramId.toBuffer(),
mintKey.toBuffer(),
],
metadataProgramId,
)
)[0];
nameSymbolAccount =
nameSymbolAccount ||
(
await PublicKey.findProgramAddress(
[
Buffer.from('metadata'),
metadataProgramId.toBuffer(),
Buffer.from(name),
Buffer.from(symbol),
],
metadataProgramId,
)
)[0];
const value = new UpdateMetadataArgs({
uri,
nonUniqueSpecificUpdateAuthority: !newNonUniqueSpecificUpdateAuthority
? undefined
: newNonUniqueSpecificUpdateAuthority,
});
const data = Buffer.from(serialize(METADATA_SCHEMA, value));
const keys = [
{
pubkey: metadataAccount,
isSigner: false,
isWritable: true,
},
{
pubkey: updateAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: nameSymbolAccount,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: metadataProgramId,
data,
}),
);
return [metadataAccount, nameSymbolAccount];
}
export async function createMetadata(
symbol: string,
name: string,
uri: string,
allowDuplicates: boolean,
updateAuthority: PublicKey,
mintKey: 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 value = new CreateMetadataArgs({ name, symbol, uri, allowDuplicates });
const data = Buffer.from(serialize(METADATA_SCHEMA, value));
const keys = [
{
pubkey: nameSymbolAccount,
isSigner: false,
isWritable: true,
},
{
pubkey: metadataAccount,
isSigner: false,
isWritable: true,
},
{
pubkey: mintKey,
isSigner: false,
isWritable: false,
},
{
pubkey: mintAuthorityKey,
isSigner: true,
isWritable: false,
},
{
pubkey: payer,
isSigner: true,
isWritable: false,
},
{
pubkey: updateAuthority,
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,
}),
);
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_PREFIX),
metadataProgramId.toBuffer(),
mintKey.toBuffer(),
],
metadataProgramId,
)
)[0];
const nameSymbolAccount = (
await PublicKey.findProgramAddress(
[
Buffer.from(METADATA_PREFIX),
metadataProgramId.toBuffer(),
Buffer.from(name),
Buffer.from(symbol),
],
metadataProgramId,
)
)[0];
const editionAccount = (
await PublicKey.findProgramAddress(
[
Buffer.from(METADATA_PREFIX),
metadataProgramId.toBuffer(),
mintKey.toBuffer(),
Buffer.from(EDITION),
],
metadataProgramId,
)
)[0];
const value = new CreateMasterEditionArgs({ maxSupply: maxSupply || null });
const data = Buffer.from(serialize(METADATA_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,
}),
);
}
export async function mintNewEditionFromMasterEditionViaToken(
newMint: PublicKey,
tokenMint: PublicKey,
newMintAuthority: PublicKey,
masterMint: PublicKey,
authorizationTokenHoldingAccount: PublicKey,
burnAuthority: PublicKey,
updateAuthorityOfMaster: PublicKey,
instructions: TransactionInstruction[],
payer: PublicKey,
) {
const metadataProgramId = programIds().metadata;
const newMetadataKey = await getMetadata(newMint);
const masterMetadataKey = await getMetadata(tokenMint);
const newEdition = await getEdition(newMint);
const masterEdition = await getEdition(tokenMint);
const data = Buffer.from([5]);
const keys = [
{
pubkey: newMetadataKey,
isSigner: false,
isWritable: true,
},
{
pubkey: newEdition,
isSigner: false,
isWritable: true,
},
{
pubkey: masterEdition,
isSigner: false,
isWritable: true,
},
{
pubkey: newMint,
isSigner: false,
isWritable: true,
},
{
pubkey: newMintAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: masterMint,
isSigner: false,
isWritable: true,
},
{
pubkey: authorizationTokenHoldingAccount,
isSigner: false,
isWritable: true,
},
{
pubkey: burnAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: payer,
isSigner: true,
isWritable: false,
},
{
pubkey: updateAuthorityOfMaster,
isSigner: false,
isWritable: false,
},
{
pubkey: masterMetadataKey,
isSigner: false,
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,
}),
);
}
export async function getNameSymbol(metadata: Metadata): Promise<PublicKey> {
const PROGRAM_IDS = programIds();
return (
await PublicKey.findProgramAddress(
[
Buffer.from(METADATA_PREFIX),
PROGRAM_IDS.metadata.toBuffer(),
metadata.mint.toBuffer(),
Buffer.from(metadata.name),
Buffer.from(metadata.symbol),
],
PROGRAM_IDS.metadata,
)
)[0];
}
export async function getEdition(tokenMint: PublicKey): Promise<PublicKey> {
const PROGRAM_IDS = programIds();
return (
await PublicKey.findProgramAddress(
[
Buffer.from(METADATA_PREFIX),
PROGRAM_IDS.metadata.toBuffer(),
tokenMint.toBuffer(),
Buffer.from(EDITION),
],
PROGRAM_IDS.metadata,
)
)[0];
}
export async function getMetadata(tokenMint: PublicKey): Promise<PublicKey> {
const PROGRAM_IDS = programIds();
return (
await PublicKey.findProgramAddress(
[
Buffer.from(METADATA_PREFIX),
PROGRAM_IDS.metadata.toBuffer(),
tokenMint.toBuffer(),
],
PROGRAM_IDS.metadata,
)
)[0];
}

View File

@ -0,0 +1,695 @@
import {
PublicKey,
SystemProgram,
SYSVAR_RENT_PUBKEY,
TransactionInstruction,
} from '@solana/web3.js';
import { programIds } from '../utils/ids';
import { deserializeBorsh } from './../utils/borsh';
import { serialize } from 'borsh';
import BN from 'bn.js';
export const VAULT_PREFIX = 'vault';
export enum VaultKey {
VaultV1 = 0,
SafetyDepositBoxV1 = 1,
ExternalPriceAccountV1 = 2,
}
export enum VaultState {
Inactive = 0,
Active = 1,
Combined = 2,
Deactivated = 3,
}
export const MAX_VAULT_SIZE =
1 + 32 + 32 + 32 + 32 + 1 + 32 + 1 + 32 + 1 + 1 + 8;
export const MAX_EXTERNAL_ACCOUNT_SIZE = 1 + 8 + 32 + 1;
export class Vault {
key: VaultKey;
/// Store token program used
tokenProgram: PublicKey;
/// Mint that produces the fractional shares
fractionMint: PublicKey;
/// Authority who can make changes to the vault
authority: PublicKey;
/// treasury where fractional shares are held for redemption by authority
fractionTreasury: PublicKey;
/// treasury where monies are held for fractional share holders to redeem(burn) shares once buyout is made
redeemTreasury: PublicKey;
/// Can authority mint more shares from fraction_mint after activation
allowFurtherShareCreation: boolean;
/// Must point at an ExternalPriceAccount, which gives permission and price for buyout.
pricingLookupAddress: PublicKey;
/// In inactive state, we use this to set the order key on Safety Deposit Boxes being added and
/// then we increment it and save so the next safety deposit box gets the next number.
/// In the Combined state during token redemption by authority, we use it as a decrementing counter each time
/// The authority of the vault withdrawals a Safety Deposit contents to count down how many
/// are left to be opened and closed down. Once this hits zero, and the fraction mint has zero shares,
/// then we can deactivate the vault.
tokenTypeCount: number;
state: VaultState;
/// Once combination happens, we copy price per share to vault so that if something nefarious happens
/// to external price account, like price change, we still have the math 'saved' for use in our calcs
lockedPricePerShare: BN;
constructor(args: {
tokenProgram: PublicKey;
fractionMint: PublicKey;
authority: PublicKey;
fractionTreasury: PublicKey;
redeemTreasury: PublicKey;
allowFurtherShareCreation: boolean;
pricingLookupAddress: PublicKey;
tokenTypeCount: number;
state: VaultState;
lockedPricePerShare: BN;
}) {
this.key = VaultKey.VaultV1;
this.tokenProgram = args.tokenProgram;
this.fractionMint = args.fractionMint;
this.authority = args.authority;
this.fractionTreasury = args.fractionTreasury;
this.redeemTreasury = args.redeemTreasury;
this.allowFurtherShareCreation = args.allowFurtherShareCreation;
this.pricingLookupAddress = args.pricingLookupAddress;
this.tokenTypeCount = args.tokenTypeCount;
this.state = args.state;
this.lockedPricePerShare = args.lockedPricePerShare;
}
}
export class SafetyDepositBox {
/// Each token type in a vault has it's own box that contains it's mint and a look-back
key: VaultKey;
/// VaultKey pointing to the parent vault
vault: PublicKey;
/// This particular token's mint
tokenMint: PublicKey;
/// Account that stores the tokens under management
store: PublicKey;
/// the order in the array of registries
order: number;
constructor(args: {
vault: PublicKey;
tokenMint: PublicKey;
store: PublicKey;
order: number;
}) {
this.key = VaultKey.SafetyDepositBoxV1;
this.vault = args.vault;
this.tokenMint = args.tokenMint;
this.store = args.store;
this.order = args.order;
}
}
export class ExternalPriceAccount {
key: VaultKey;
pricePerShare: BN;
/// Mint of the currency we are pricing the shares against, should be same as redeem_treasury.
/// Most likely will be USDC mint most of the time.
priceMint: PublicKey;
/// Whether or not combination has been allowed for this vault.
allowedToCombine: boolean;
constructor(args: {
pricePerShare: BN;
priceMint: PublicKey;
allowedToCombine: boolean;
}) {
this.key = VaultKey.ExternalPriceAccountV1;
this.pricePerShare = args.pricePerShare;
this.priceMint = args.priceMint;
this.allowedToCombine = args.allowedToCombine;
}
}
class InitVaultArgs {
instruction: number = 0;
allowFurtherShareCreation: boolean = false;
constructor(args: { allowFurtherShareCreation: boolean }) {
this.allowFurtherShareCreation = args.allowFurtherShareCreation;
}
}
class AmountArgs {
instruction: number;
amount: BN;
constructor(args: { instruction: number; amount: BN }) {
this.instruction = args.instruction;
this.amount = args.amount;
}
}
class NumberOfShareArgs {
instruction: number;
numberOfShares: BN;
constructor(args: { instruction: number; numberOfShares: BN }) {
this.instruction = args.instruction;
this.numberOfShares = args.numberOfShares;
}
}
class UpdateExternalPriceAccountArgs {
instruction: number = 9;
externalPriceAccount: ExternalPriceAccount;
constructor(args: { externalPriceAccount: ExternalPriceAccount }) {
this.externalPriceAccount = args.externalPriceAccount;
}
}
export const VAULT_SCHEMA = new Map<any, any>([
[
InitVaultArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['allowFurtherShareCreation', 'u8'],
],
},
],
[
AmountArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['amount', 'u64'],
],
},
],
[
NumberOfShareArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['numberOfShares', 'u64'],
],
},
],
[
UpdateExternalPriceAccountArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['externalPriceAccount', ExternalPriceAccount],
],
},
],
[
Vault,
{
kind: 'struct',
fields: [
['key', 'u8'],
['tokenProgram', 'pubkey'],
['fractionMint', 'pubkey'],
['authority', 'pubkey'],
['fractionTreasury', 'pubkey'],
['redeemTreasury', 'pubkey'],
['allowFurtherShareCreation', 'u8'],
['pricingLookupAddress', 'u8'],
['tokenTypeCount', 'u8'],
['state', 'u8'],
['lockedPricePerShare', 'u64'],
],
},
],
[
SafetyDepositBox,
{
kind: 'struct',
fields: [
['key', 'u8'],
['vault', 'pubkey'],
['tokenMint', 'pubkey'],
['store', 'pubkey'],
['order', 'u8'],
],
},
],
[
ExternalPriceAccount,
{
kind: 'struct',
fields: [
['key', 'u8'],
['pricePerShare', 'u64'],
['priceMint', 'pubkey'],
['allowedToCombine', 'u8'],
],
},
],
]);
export const decodeVault = (buffer: Buffer) => {
return deserializeBorsh(VAULT_SCHEMA, Vault, buffer) as Vault;
};
export const decodeSafetyDeposit = (buffer: Buffer) => {
return deserializeBorsh(
VAULT_SCHEMA,
SafetyDepositBox,
buffer,
) as SafetyDepositBox;
};
export async function initVault(
allowFurtherShareCreation: boolean,
fractionalMint: PublicKey,
redeemTreasury: PublicKey,
fractionalTreasury: PublicKey,
vault: PublicKey,
vaultAuthority: PublicKey,
pricingLookupAddress: PublicKey,
instructions: TransactionInstruction[],
) {
const vaultProgramId = programIds().vault;
const data = Buffer.from(
serialize(VAULT_SCHEMA, new InitVaultArgs({ allowFurtherShareCreation })),
);
const keys = [
{
pubkey: fractionalMint,
isSigner: false,
isWritable: true,
},
{
pubkey: redeemTreasury,
isSigner: false,
isWritable: true,
},
{
pubkey: fractionalTreasury,
isSigner: false,
isWritable: true,
},
{
pubkey: vault,
isSigner: false,
isWritable: true,
},
{
pubkey: vaultAuthority,
isSigner: false,
isWritable: false,
},
{
pubkey: pricingLookupAddress,
isSigner: false,
isWritable: false,
},
{
pubkey: programIds().token,
isSigner: false,
isWritable: false,
},
{
pubkey: SYSVAR_RENT_PUBKEY,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: vaultProgramId,
data: data,
}),
);
}
export async function getSafetyDepositBox(
vault: PublicKey,
tokenMint: PublicKey,
): Promise<PublicKey> {
const vaultProgramId = programIds().vault;
return (
await PublicKey.findProgramAddress(
[Buffer.from(VAULT_PREFIX), vault.toBuffer(), tokenMint.toBuffer()],
vaultProgramId,
)
)[0];
}
export async function addTokenToInactiveVault(
amount: BN,
tokenMint: PublicKey,
tokenAccount: PublicKey,
tokenStoreAccount: PublicKey,
vault: PublicKey,
vaultAuthority: PublicKey,
payer: PublicKey,
transferAuthority: PublicKey,
instructions: TransactionInstruction[],
) {
const vaultProgramId = programIds().vault;
const safetyDepositBox: PublicKey = await getSafetyDepositBox(
vault,
tokenMint,
);
const value = new AmountArgs({
instruction: 1,
amount,
});
const data = Buffer.from(serialize(VAULT_SCHEMA, value));
const keys = [
{
pubkey: safetyDepositBox,
isSigner: false,
isWritable: true,
},
{
pubkey: tokenAccount,
isSigner: false,
isWritable: true,
},
{
pubkey: tokenStoreAccount,
isSigner: false,
isWritable: true,
},
{
pubkey: vault,
isSigner: false,
isWritable: true,
},
{
pubkey: vaultAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: payer,
isSigner: true,
isWritable: false,
},
{
pubkey: transferAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: programIds().token,
isSigner: false,
isWritable: false,
},
{
pubkey: SYSVAR_RENT_PUBKEY,
isSigner: false,
isWritable: false,
},
{
pubkey: SystemProgram.programId,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: vaultProgramId,
data,
}),
);
}
export async function activateVault(
numberOfShares: BN,
vault: PublicKey,
fractionMint: PublicKey,
fractionTreasury: PublicKey,
vaultAuthority: PublicKey,
instructions: TransactionInstruction[],
) {
const vaultProgramId = programIds().vault;
const fractionMintAuthority = (
await PublicKey.findProgramAddress(
[Buffer.from(VAULT_PREFIX), vaultProgramId.toBuffer()],
vaultProgramId,
)
)[0];
const value = new NumberOfShareArgs({ instruction: 2, numberOfShares });
const data = Buffer.from(serialize(VAULT_SCHEMA, value));
const keys = [
{
pubkey: vault,
isSigner: false,
isWritable: true,
},
{
pubkey: fractionMint,
isSigner: false,
isWritable: true,
},
{
pubkey: fractionTreasury,
isSigner: false,
isWritable: true,
},
{
pubkey: fractionMintAuthority,
isSigner: false,
isWritable: false,
},
{
pubkey: vaultAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: programIds().token,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: vaultProgramId,
data,
}),
);
}
export async function combineVault(
vault: PublicKey,
outstandingShareTokenAccount: PublicKey,
payingTokenAccount: PublicKey,
fractionMint: PublicKey,
fractionTreasury: PublicKey,
redeemTreasury: PublicKey,
newVaultAuthority: PublicKey | undefined,
vaultAuthority: PublicKey,
transferAuthority: PublicKey,
externalPriceAccount: PublicKey,
instructions: TransactionInstruction[],
) {
const vaultProgramId = programIds().vault;
const burnAuthority = (
await PublicKey.findProgramAddress(
[Buffer.from(VAULT_PREFIX), vaultProgramId.toBuffer()],
vaultProgramId,
)
)[0];
const data = Buffer.from([3]);
const keys = [
{
pubkey: vault,
isSigner: false,
isWritable: true,
},
{
pubkey: outstandingShareTokenAccount,
isSigner: false,
isWritable: true,
},
{
pubkey: payingTokenAccount,
isSigner: false,
isWritable: true,
},
{
pubkey: fractionMint,
isSigner: false,
isWritable: true,
},
{
pubkey: fractionTreasury,
isSigner: false,
isWritable: true,
},
{
pubkey: redeemTreasury,
isSigner: false,
isWritable: true,
},
{
pubkey: newVaultAuthority || vaultAuthority,
isSigner: false,
isWritable: false,
},
{
pubkey: vaultAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: transferAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: burnAuthority,
isSigner: false,
isWritable: false,
},
{
pubkey: externalPriceAccount,
isSigner: false,
isWritable: false,
},
{
pubkey: programIds().token,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: vaultProgramId,
data,
}),
);
}
export async function withdrawTokenFromSafetyDepositBox(
amount: BN,
destination: PublicKey,
safetyDepositBox: PublicKey,
storeKey: PublicKey,
vault: PublicKey,
fractionMint: PublicKey,
vaultAuthority: PublicKey,
instructions: TransactionInstruction[],
) {
const vaultProgramId = programIds().vault;
const transferAuthority = (
await PublicKey.findProgramAddress(
[Buffer.from(VAULT_PREFIX), vaultProgramId.toBuffer()],
vaultProgramId,
)
)[0];
const value = new AmountArgs({ instruction: 5, amount });
const data = Buffer.from(serialize(VAULT_SCHEMA, value));
const keys = [
{
pubkey: destination,
isSigner: false,
isWritable: true,
},
{
pubkey: safetyDepositBox,
isSigner: false,
isWritable: true,
},
{
pubkey: storeKey,
isSigner: false,
isWritable: true,
},
{
pubkey: vault,
isSigner: false,
isWritable: true,
},
{
pubkey: fractionMint,
isSigner: false,
isWritable: true,
},
{
pubkey: vaultAuthority,
isSigner: true,
isWritable: false,
},
{
pubkey: transferAuthority,
isSigner: false,
isWritable: false,
},
{
pubkey: programIds().token,
isSigner: false,
isWritable: false,
},
{
pubkey: SYSVAR_RENT_PUBKEY,
isSigner: false,
isWritable: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: vaultProgramId,
data,
}),
);
}
export async function updateExternalPriceAccount(
externalPriceAccountKey: PublicKey,
externalPriceAccount: ExternalPriceAccount,
instructions: TransactionInstruction[],
) {
const vaultProgramId = programIds().vault;
const value = new UpdateExternalPriceAccountArgs({ externalPriceAccount });
const data = Buffer.from(serialize(VAULT_SCHEMA, value));
console.log('Data', data);
const keys = [
{
pubkey: externalPriceAccountKey,
isSigner: false,
isWritable: true,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: vaultProgramId,
data,
}),
);
}

View File

@ -1,7 +1,6 @@
import React from 'react';
import { Button, Popover } from 'antd';
import { CurrentUserBadge } from '../CurrentUserBadge';
import { CurrentUserWalletBadge } from '../CurrentUserWalletBadge';
import { SettingOutlined } from '@ant-design/icons';
import { Settings } from '../Settings';
import { LABELS } from '../../constants/labels';
@ -20,11 +19,7 @@ export const AppBar = (props: {
<div className="App-Bar-right">
{props.left}
{connected ? (
props.useWalletBadge ? (
<CurrentUserWalletBadge />
) : (
<CurrentUserBadge />
)
<CurrentUserBadge />
) : (
<ConnectButton
type="text"

View File

@ -1,6 +1,6 @@
.App-Bar {
-webkit-box-pack: justify;
justify-content: space-between;
justify-content: space-between !important;
-webkit-box-align: center;
align-items: center;
flex-direction: row;

View File

@ -1,15 +1,15 @@
import { Button, Dropdown, Menu } from "antd";
import { ButtonProps } from "antd/lib/button";
import React from "react";
import { Button, Dropdown, Menu } from 'antd';
import { ButtonProps } from 'antd/lib/button';
import React from 'react';
import { useWallet } from './../../contexts/wallet';
export interface ConnectButtonProps extends ButtonProps, React.RefAttributes<HTMLElement> {
export interface ConnectButtonProps
extends ButtonProps,
React.RefAttributes<HTMLElement> {
allowWalletChange?: boolean;
}
export const ConnectButton = (
props: ConnectButtonProps
) => {
export const ConnectButton = (props: ConnectButtonProps) => {
const { connected, connect, select, provider } = useWallet();
const { onClick, children, disabled, allowWalletChange, ...rest } = props;
@ -17,25 +17,30 @@ export const ConnectButton = (
const menu = (
<Menu>
<Menu.Item key="3" onClick={select}>Change Wallet</Menu.Item>
<Menu.Item key="3" onClick={select}>
Change Wallet
</Menu.Item>
</Menu>
);
if(!provider || !allowWalletChange) {
return <Button
{...rest}
onClick={connected ? onClick : connect}
disabled={connected && disabled}
>
{connected ? props.children : 'Connect'}
</Button>;
if (!provider || !allowWalletChange) {
return (
<Button
{...rest}
onClick={connected ? onClick : connect}
disabled={connected && disabled}
>
{connected ? props.children : 'Connect'}
</Button>
);
}
return (
<Dropdown.Button
onClick={connected ? onClick : connect}
disabled={connected && disabled}
overlay={menu}>
onClick={connected ? onClick : connect}
disabled={connected && disabled}
overlay={menu}
>
Connect
</Dropdown.Button>
);

Some files were not shown because too many files have changed in this diff Show More