Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TAS-415] ✨ Use arweave v2 bundlr upload API for ISCN single file upload #390

Merged
merged 6 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 19 additions & 28 deletions components/IscnRegisterForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -592,13 +592,12 @@ import { namespace } from 'vuex-class'
import { AxiosResponse } from 'axios'
import { Author } from '~/types/author'

import { estimateBundlrFilePrice, uploadSingleFileToBundlr } from '~/utils/arweave/v2'
import { signISCNTx } from '~/utils/cosmos/iscn';
import { DEFAULT_TRANSFER_FEE, sendLIKE } from '~/utils/cosmos/sign';
import { estimateISCNTxGasAndFee, formatISCNTxPayload } from '~/utils/cosmos/iscn/sign';
import {
getLikerIdMinApi,
API_POST_ARWEAVE_ESTIMATE,
API_POST_ARWEAVE_UPLOAD,
API_POST_NUMBERS_PROTOCOL_ASSETS,
} from '~/constant/api';
import { getAccountBalance } from '~/utils/cosmos'
Expand Down Expand Up @@ -790,6 +789,10 @@ export default class IscnRegisterForm extends Vue {
return undefined
}

get fileByteSize() {
return this.fileBlob?.size || 0
}

get payload() {
return {
type: this.type,
Expand Down Expand Up @@ -1119,18 +1122,10 @@ export default class IscnRegisterForm extends Vue {
}

async estimateArweaveFee(): Promise<void> {
const formData = new FormData();
if (this.fileBlob) formData.append('file', this.fileBlob);
try {
const { address, arweaveId, LIKE } = await this.$axios.$post(
API_POST_ARWEAVE_ESTIMATE,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
},
);
const { address, arweaveId, LIKE } = await estimateBundlrFilePrice({
fileSize: this.fileByteSize, ipfsHash: this.ipfsHash,
})
this.uploadArweaveId = arweaveId;
if (arweaveId) {
this.$emit('arweaveUploaded', { arweaveId })
Expand All @@ -1150,7 +1145,7 @@ export default class IscnRegisterForm extends Vue {
if (!this.signer) throw new Error('SIGNER_NOT_INITED');
if (!this.arweaveFeeTargetAddress) throw new Error('TARGET_ADDRESS_NOT_SET');
this.uploadStatus = 'signing';
const memo = JSON.stringify({ ipfs: this.ipfsHash });
const memo = JSON.stringify({ ipfs: this.ipfsHash, fileSize: this.fileByteSize });
try {
const { transactionHash } = await sendLIKE(this.address, this.arweaveFeeTargetAddress, this.arweaveFee.toFixed(), this.signer, memo);
return transactionHash;
Expand All @@ -1168,30 +1163,26 @@ export default class IscnRegisterForm extends Vue {
async submitToArweave(): Promise<void> {
logTrackerEvent(this, 'ISCNCreate', 'SubmitToArweave', '', 1);
if (this.uploadArweaveId) return;
if (!this.fileBlob) return;
this.isOpenSignDialog = true;
this.onOpenKeplr();
const transactionHash = await this.sendArweaveFeeTx();
const formData = new FormData();
if (this.fileBlob) formData.append('file', this.fileBlob);

// Register Numbers Protocol assets along with Arweave
if (this.isRegisterNumbersProtocolAsset) {
logTrackerEvent(this, 'ISCNCreate', 'SubmitToNumbers', '', 1);
formData.append('num', '1')
}
this.isUploadingArweave = true;
this.uploadStatus = 'uploading';
try {
const {
arweaveId,
} = await this.$axios.$post(
`${API_POST_ARWEAVE_UPLOAD}?txHash=${transactionHash}`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
},
)
const arrayBuffer = await this.fileBlob.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
const arweaveId = await uploadSingleFileToBundlr(buffer, {
fileSize: this.fileByteSize,
ipfsHash: this.ipfsHash,
fileType: this.fileType as string,
txHash: transactionHash,
});
if (arweaveId) {
this.uploadArweaveId = arweaveId
this.$emit('arweaveUploaded', { arweaveId })
Expand Down
4 changes: 2 additions & 2 deletions components/IscnUploadForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ import exifr from 'exifr'
import Hash from 'ipfs-only-hash'
import { Vue, Component, Prop } from 'vue-property-decorator'
import { logTrackerEvent } from '~/utils/logger'
import { IS_CHAIN_UPGRADING } from '~/constant'
import { IS_CHAIN_UPGRADING, UPLOAD_FILESIZE_MAX } from '~/constant'

import {
fileToArrayBuffer,
Expand Down Expand Up @@ -224,7 +224,7 @@ export default class UploadForm extends Vue {

if (files && files[0]) {
const reader = new FileReader()
if (files[0].size < 30000000) {
if (files[0].size < UPLOAD_FILESIZE_MAX) {
this.fileName = files[0].name
this.fileSize = files[0].size
this.fileType = `${files[0].type}`
Expand Down
4 changes: 4 additions & 0 deletions constant/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ const LIKECOIN_CHAIN_API = IS_TESTNET ? 'https://node.testnet.like.co' : 'https:
export const LIKER_NFT_TARGET_ADDRESS = IS_TESTNET ? 'like1yney2cqn5qdrlc50yr5l53898ufdhxafqz9gxp' : 'like17m4vwrnhjmd20uu7tst7nv0kap6ee7js69jfrs';
export const API_POST_ARWEAVE_ESTIMATE = `${LIKE_CO_API_ROOT}/arweave/estimate`;
export const API_POST_ARWEAVE_UPLOAD = `${LIKE_CO_API_ROOT}/arweave/upload`;
export const API_GET_ARWEAVE_V2_PUBLIC_KEY = `${LIKE_CO_API_ROOT}/arweave/v2/public_key`;
export const API_POST_ARWEAVE_V2_ESTIMATE = `${LIKE_CO_API_ROOT}/arweave/v2/estimate`;
export const API_POST_ARWEAVE_V2_SIGN = `${LIKE_CO_API_ROOT}/arweave/v2/sign_payment_data`;
export const API_POST_ARWEAVE_V2_REGISTER = `${LIKE_CO_API_ROOT}/arweave/v2/register`;
export const API_LIKER_NFT_MINT = `${LIKE_CO_API_ROOT}/likernft/mint`;
export const API_LIKER_NFT_MINT_IMAGE = `${LIKE_CO_API_ROOT}/likernft/mint/image`;
export const API_LIKER_NFT_PURCHASE = `${LIKE_CO_API_ROOT}/likernft/purchase`;
Expand Down
2 changes: 2 additions & 0 deletions constant/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,6 @@ export const LIKECOIN_CHAIN_STAKING_ENDPOINT = IS_TESTNET
? 'https://likecoin-public-testnet-5.netlify.app/validators'
: 'https://dao.like.co/validators';

export const UPLOAD_FILESIZE_MAX = 100000000; // 100MB


16 changes: 16 additions & 0 deletions nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@
'data:',
'*',
],
'frame-src': [

Check warning on line 77 in nuxt.config.js

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 16)

There should be no linebreak after '['
'verify.walletconnect.com',
],

Check warning on line 79 in nuxt.config.js

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 16)

There should be no linebreak before ']'
'style-src': [
"'self'",
"'unsafe-inline'",
Expand Down Expand Up @@ -168,12 +168,28 @@
'@likecoin/iscn-js',
'@likecoin/wallet-connector',
'@walletconnect',
'@bundlr-network',
'@noble/curves',
'arbundle',
],
extend(config, ctx) {
/* eslint-disable no-param-reassign */
if (!ctx.isDev) {
config.resolve.alias['bn.js'] = path.join(__dirname, './node_modules/bn.js');
}
if (ctx.isClient) {
config.resolve.alias['arbundles/web'] = path.join(__dirname, './node_modules/arbundles/build/web/esm/webIndex');
} else {
config.externals = {
'@bundlr-network/client': '@bundlr-network/client',
}
// config.resolve.alias['arbundles/node'] = path.join(__dirname, './node_modules/arbundles/build/node/cjs');
}
config.module.rules.push({
test: /\.mjs$/,
include: /node_modules/,
type: 'javascript/auto',
});
/* eslint-enable no-param-reassign */
},
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"test": "jest"
},
"dependencies": {
"@bundlr-network/client": "^0.11.17",
"@cosmjs/proto-signing": "^0.30.1",
"@cosmjs/stargate": "^0.30.1",
"@keplr-wallet/types": "^0.11.4",
Expand Down
170 changes: 170 additions & 0 deletions utils/arweave/v2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import axios from 'axios'

import { IS_TESTNET } from '~/constant'
import {
API_POST_ARWEAVE_V2_SIGN,
API_GET_ARWEAVE_V2_PUBLIC_KEY,
API_POST_ARWEAVE_V2_ESTIMATE,
API_POST_ARWEAVE_V2_REGISTER,
} from '~/constant/api'

class Provider {
pubKey: Buffer | null = null
fileSize = 0
ipfsHash = ''
txHash = ''

constructor({
publicKey,
fileSize,
ipfsHash,
txHash,
}: {
publicKey: string
fileSize: number
ipfsHash: string
txHash: string
}) {
this.pubKey = Buffer.from(publicKey, 'base64');
this.fileSize = fileSize
this.ipfsHash = ipfsHash
this.txHash = txHash
}

setLikeCoinTxInfo({
fileSize,
ipfsHash,
txHash,
}: {
fileSize: number
ipfsHash: string
txHash: string
}) {
this.fileSize = fileSize
this.ipfsHash = ipfsHash
this.txHash = txHash
}

setPublicKey(newPubKey: string) {
this.pubKey = Buffer.from(newPubKey, 'base64');
}

getPublicKey() {
return this.pubKey
}

getSigner = () => ({
getAddress: () => this.pubKey?.toString(),
_signTypedData: async (
_domain: never,
_types: never,
message: { address: string; 'Transaction hash': Uint8Array },
) => {
const convertedMsg = Buffer.from(message['Transaction hash']).toString(
'base64',
)
const res = await axios.post(API_POST_ARWEAVE_V2_SIGN, {
signatureData: convertedMsg,
fileSize: this.fileSize,
ipfsHash: this.ipfsHash,
txHash: this.txHash,
})
const { signature } = await res.data
const bSig = Buffer.from(signature, 'base64')
// pad & convert so it's in the format the signer expects to have to convert from.
const pad = Buffer.concat([
Buffer.from([0]),
Buffer.from(bSig),
]).toString(
'hex',
)
return pad
},
})

// eslint-disable-next-line no-underscore-dangle, class-methods-use-this
_ready() {}
}

let WebBundlr: any = null;

async function getProvider({
fileSize,
ipfsHash,
txHash,
}: {
fileSize: number
ipfsHash: string
txHash: string
}) {
const { data } = await axios.get(API_GET_ARWEAVE_V2_PUBLIC_KEY)
const { publicKey } = data
const provider = new Provider({ publicKey, fileSize, ipfsHash, txHash })
return provider
}

async function getBundler({
fileSize,
ipfsHash,
txHash,
}: {
fileSize: number
ipfsHash: string
txHash: string
}) {
if (!WebBundlr) {
WebBundlr = (await import('@bundlr-network/client')).default;
}
const p = await getProvider({ fileSize, ipfsHash, txHash })
const bundlr = new WebBundlr(
IS_TESTNET
? 'https://devnet.bundlr.network'
: 'https://node1.bundlr.network',
'matic',
p,
)
await bundlr.ready()
return bundlr
}

export async function estimateBundlrFilePrice({
fileSize,
ipfsHash,
}: {
fileSize: number
ipfsHash: string
}) {
const { data } = await axios.post(API_POST_ARWEAVE_V2_ESTIMATE, {
fileSize,
ipfsHash,
})
return data
}

export async function uploadSingleFileToBundlr(
file: Buffer,
{
fileType,
fileSize,
ipfsHash,
txHash,
}: { fileSize: number; fileType?: string, ipfsHash: string; txHash: string },
) {
const bundler = await getBundler({ fileSize, ipfsHash, txHash })
const tags = [
{ name: 'IPFS-Add', value: ipfsHash },
{ name: 'standard', value: 'v0.1'},
];
if (fileType) tags.push({ name: 'Content-Type', value: fileType })
const response = await bundler.upload(file, { tags })
const arweaveId = response.id;
if (arweaveId) {
await axios.post(API_POST_ARWEAVE_V2_REGISTER, {
fileSize,
ipfsHash,
txHash,
arweaveId,
});
}
return arweaveId;
}
Loading
Loading