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
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
@@ -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'
@@ -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,
@@ -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 })
@@ -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;
@@ -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 })
4 changes: 2 additions & 2 deletions components/IscnUploadForm.vue
Original file line number Diff line number Diff line change
@@ -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,
@@ -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}`
4 changes: 4 additions & 0 deletions constant/api.ts
Original file line number Diff line number Diff line change
@@ -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`;
2 changes: 2 additions & 0 deletions constant/index.ts
Original file line number Diff line number Diff line change
@@ -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
@@ -168,12 +168,28 @@ export default {
'@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 */
},
},
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
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