Shared wallets
Pre-requisites
- how to start a server
- In order to be able to send transactions, our wallet must have funds. In case of
preview
andpreprod
testnets we can request tADA from the faucet.
Overview
This guide shows you how to create a shared wallet and make a shared transaction, providing witnesses from all required co-signers referenced in the payment_script_template
and delegation_script_template
fields. In this example, we'll create two shared wallets using the same template for both payment and delegation operations. The template that we'll use will indicate that we require signatures from all co-owners in order to make a transaction. In our case, the wallet will have two co-owners cosigner#0
and cosigner#1
:
"template":
{ "all":
[ "cosigner#0", "cosigner#1"]
}
The script templates use Cardano simple scripts therefore one can build more sophisticated templates to guard their shared wallet spending or delegation operations. Below are some examples of possible templates. You can also explore POST /shared-wallets
swagger specification for more details.
Template examples
- required signature from any cosigner from the list
"template":
{ "any":
[ "cosigner#0", "cosigner#1", "cosigner#2"]
}
- required signature from at least 2 cosigners from the list
"template":
{ "some":
{ "at_least": 2,
"from": [ "cosigner#0", "cosigner#1", "cosigner#2"]
}
}
- required signature from at least 1 cosigners from the list but with additional nested constraints (at least
cosigner#0
or at leastcosigner#1
or at least bothcosigner#2
andcosigner#3
and any ofcosigner#4
andcosigner#5
)
"template":{
"some":{
"at_least":1,
"from":[
"cosigner#0",
"cosigner#1",
{
"all":[
"cosigner#2",
"cosigner#3",
{
"any":[
"cosigner#4",
"cosigner#5"
]
}
]
}
]
}
}
- required signature from any cosigner but with additional time locking constraints (either
cosigner#0
orcosigner#1
but only until slot 18447928 orcosigner#2
but only from slot 18447928)
"template":{
"any":[
"cosigner#0",
{
"all":[
"cosigner#1",
{
"active_until":18447928
}
]
},
{
"all":[
"cosigner#2",
{
"active_from":18447928
}
]
}
]
}
Creating wallets
First let's create two incomplete
shared wallets using the POST /shared-wallets
endpoint. The wallets are marked as "incomplete" because they do not yet have public keys from all cosigners.
Cosigner#0
wallet
> curl -X POST http://localhost:8090/v2/shared-wallets \
-d '{
"mnemonic_sentence":[
"beach",
"want",
"fly",
"guess",
"cabbage",
"hybrid",
"profit",
"leaf",
"term",
"air",
"join",
"feel",
"sting",
"nurse",
"anchor",
"come",
"entire",
"oil",
"kidney",
"situate",
"fun",
"deal",
"palm",
"chimney"
],
"passphrase":"Secure Passphrase",
"name":"`Cosigner#0` wallet",
"account_index":"0H",
"payment_script_template":{
"cosigners":{
"cosigner#0":"self"
},
"template":{
"all":[
"cosigner#0",
"cosigner#1"
]
}
},
"delegation_script_template":{
"cosigners":{
"cosigner#0":"self"
},
"template":{
"all":[
"cosigner#0",
"cosigner#1"
]
}
}
}' \
-H "Content-Type: application/json"
Cosigner#1
wallet
> curl -X POST http://localhost:8090/v2/shared-wallets \
-d '{
"mnemonic_sentence":[
"slight",
"tool",
"pear",
"write",
"body",
"fruit",
"crucial",
"tomorrow",
"hunt",
"alley",
"object",
"tool",
"voyage",
"loud",
"loop",
"client",
"access",
"vocal",
"unable",
"brand",
"patch",
"remain",
"object",
"boat"
],
"passphrase":"Secure Passphrase",
"name":"`Cosigner#1` wallet",
"account_index":"0H",
"payment_script_template":{
"cosigners":{
"cosigner#1":"self"
},
"template":{
"all":[
"cosigner#0",
"cosigner#1"
]
}
},
"delegation_script_template":{
"cosigners":{
"cosigner#1":"self"
},
"template":{
"all":[
"cosigner#0",
"cosigner#1"
]
}
}
}' \
-H "Content-Type: application/json"
Notice that the templates payment_script_template
and delegation_script_template
are partially the same for both wallets:
"template":{
"all":[
"cosigner#0",
"cosigner#1"
]
}
However the "cosigners":
part differs slightly. "Cosigner#0
wallet" has "cosigner#0":"self"
and "Cosigner#1
wallet" - "cosigner#1":"self"
. Each wallet has partial information about cosigners. "Cosigner#0
wallet" only knows about cosigner#0
and "Cosigner#1
wallet" only knows about cosigner#1
.
Now we can look up the wallets we've just created using the GET /shared-wallets/{walletId}
endpoint. From the response, we can tell that the wallet's status is "incomplete". For instance:
> curl -X GET http://localhost:8090/v2/shared-wallets/5e46668c320bb4568dd25551e0c33b0539668aa8 | jq
{
"account_index": "0H",
"address_pool_gap": 20,
"delegation_script_template": {
"cosigners": {
"cosigner#1": "acct_shared_xvk1mqrjad6aklkpwvhhktrzeef5yunla9pjs0jt9csp3gjcynxvumfjpk99hqxkyknn30ya6l5yjgeegs5ltmmsy70gm500sacvllvwt6qjztknp"
},
"template": {
"all": [
"cosigner#0",
"cosigner#1"
]
}
},
"id": "5e46668c320bb4568dd25551e0c33b0539668aa8",
"name": "`Cosigner#1` wallet",
"payment_script_template": {
"cosigners": {
"cosigner#1": "acct_shared_xvk1mqrjad6aklkpwvhhktrzeef5yunla9pjs0jt9csp3gjcynxvumfjpk99hqxkyknn30ya6l5yjgeegs5ltmmsy70gm500sacvllvwt6qjztknp"
},
"template": {
"all": [
"cosigner#0",
"cosigner#1"
]
}
},
"state": {
"status": "incomplete"
}
}
Patching wallets
In order to be able to spend from the wallets we need to patch their payment and delegation templates with missing cosigners.
We already know that "Cosigner#0
wallet" is missing the cosigner#1
key and "Cosigner#1
wallet" is missing the cosigner#0
key.
First, let's get the cosigner#1
key from the "Cosigner#1
wallet". We can use the GET /shared-wallets/{walletId}/keys
endpoint for this:
> curl -X GET http://localhost:8090/v2/shared-wallets/5e46668c320bb4568dd25551e0c33b0539668aa8/keys?format=extended
"acct_shared_xvk1mqrjad6aklkpwvhhktrzeef5yunla9pjs0jt9csp3gjcynxvumfjpk99hqxkyknn30ya6l5yjgeegs5ltmmsy70gm500sacvllvwt6qjztknp"
Now we can patch the payment and delegation templates of the "Cosigner#0
wallet" with the cosigner#1
key using the PATCH /shared-wallets/{walletId}/payment-script-template
and PATCH /shared-wallets/{walletId}/delegation-script-template
endpoints, respectively.
> curl -X PATCH http://localhost:8090/v2/shared-wallets/2a0ebd0cceab2161765badf2e389b26e0961de2f/payment-script-template \
-d '{"cosigner#1":"acct_shared_xvk1mqrjad6aklkpwvhhktrzeef5yunla9pjs0jt9csp3gjcynxvumfjpk99hqxkyknn30ya6l5yjgeegs5ltmmsy70gm500sacvllvwt6qjztknp"}' \
-H "Content-Type: application/json"
> curl -X PATCH http://localhost:8090/v2/shared-wallets/2a0ebd0cceab2161765badf2e389b26e0961de2f/delegation-script-template \
-d '{"cosigner#1":"acct_shared_xvk1mqrjad6aklkpwvhhktrzeef5yunla9pjs0jt9csp3gjcynxvumfjpk99hqxkyknn30ya6l5yjgeegs5ltmmsy70gm500sacvllvwt6qjztknp"}' \
-H "Content-Type: application/json"
We'll repeat the same action or "Cosigner#1
wallet", i.e. first get cosigner#0
key from "Cosigner#0
wallet" and then patch the payment and delegation templates of "Cosigner#1
wallet" with it.
> curl -X GET http://localhost:8090/v2/shared-wallets/2a0ebd0cceab2161765badf2e389b26e0961de2f/keys?format=extended
"acct_shared_xvk1lk5eg5unj4fg48vamr9pc40euump5qs084a7cdgvs9u6yn82nh9lr3s62asfv45r4s0fqa239j3dc5e4f7z24heug43zpg6qdy5kawq63hhzl"
> curl -X PATCH http://localhost:8090/v2/shared-wallets/5e46668c320bb4568dd25551e0c33b0539668aa8/payment-script-template \
-d '{"cosigner#0":"acct_shared_xvk1lk5eg5unj4fg48vamr9pc40euump5qs084a7cdgvs9u6yn82nh9lr3s62asfv45r4s0fqa239j3dc5e4f7z24heug43zpg6qdy5kawq63hhzl"}' \
-H "Content-Type: application/json"
> curl -X PATCH http://localhost:8090/v2/shared-wallets/5e46668c320bb4568dd25551e0c33b0539668aa8/delegation-script-template \
-d '{"cosigner#0":"acct_shared_xvk1lk5eg5unj4fg48vamr9pc40euump5qs084a7cdgvs9u6yn82nh9lr3s62asfv45r4s0fqa239j3dc5e4f7z24heug43zpg6qdy5kawq63hhzl"}' \
-H "Content-Type: application/json"
Now, if we look up the wallet again with GET /shared-wallets/{walletId}
, we should notice that the wallet has started restoring, and after a while it is ready to use:
> curl -X GET http://localhost:8090/v2/shared-wallets/2a0ebd0cceab2161765badf2e389b26e0961de2f | jq .state
{
"status": "ready"
}
Furthermore, the wallets now have complete information about cosigners for delegation and payment templates:
> curl -X GET http://localhost:8090/v2/shared-wallets/2a0ebd0cceab2161765badf2e389b26e0961de2f | jq .payment_script_template
{
"cosigners": {
"cosigner#0": "acct_shared_xvk1lk5eg5unj4fg48vamr9pc40euump5qs084a7cdgvs9u6yn82nh9lr3s62asfv45r4s0fqa239j3dc5e4f7z24heug43zpg6qdy5kawq63hhzl",
"cosigner#1": "acct_shared_xvk1mqrjad6aklkpwvhhktrzeef5yunla9pjs0jt9csp3gjcynxvumfjpk99hqxkyknn30ya6l5yjgeegs5ltmmsy70gm500sacvllvwt6qjztknp"
},
"template": {
"all": [
"cosigner#0",
"cosigner#1"
]
}
}
> curl -X GET http://localhost:8090/v2/shared-wallets/2a0ebd0cceab2161765badf2e389b26e0961de2f | jq .delegation_script_template
{
"cosigners": {
"cosigner#0": "acct_shared_xvk1lk5eg5unj4fg48vamr9pc40euump5qs084a7cdgvs9u6yn82nh9lr3s62asfv45r4s0fqa239j3dc5e4f7z24heug43zpg6qdy5kawq63hhzl",
"cosigner#1": "acct_shared_xvk1mqrjad6aklkpwvhhktrzeef5yunla9pjs0jt9csp3gjcynxvumfjpk99hqxkyknn30ya6l5yjgeegs5ltmmsy70gm500sacvllvwt6qjztknp"
},
"template": {
"all": [
"cosigner#0",
"cosigner#1"
]
}
}
Spending transactions
Our shared wallets are now fully-operational. In particular, we can access the addresses of the wallets via the GET /shared-wallets/{walletId}/addresses
endpoint. We can get the address and fund it from the faucet so that we can spend from our wallet later on.
After we have funded the "Cosigner#0
wallet", we can see that the balance has changed on the "Cosigner#1
wallet" as well. Both wallets have the same balance:
> curl -X GET http://localhost:8090/v2/shared-wallets/2a0ebd0cceab2161765badf2e389b26e0961de2f | jq .balance
{
"available": {
"quantity": 10000000000,
"unit": "lovelace"
},
"reward": {
"quantity": 0,
"unit": "lovelace"
},
"total": {
"quantity": 10000000000,
"unit": "lovelace"
}
}
> curl -X GET http://localhost:8090/v2/shared-wallets/5e46668c320bb4568dd25551e0c33b0539668aa8 | jq .balance
{
"available": {
"quantity": 10000000000,
"unit": "lovelace"
},
"reward": {
"quantity": 0,
"unit": "lovelace"
},
"total": {
"quantity": 10000000000,
"unit": "lovelace"
}
}
Of course, this is expected. Both co-owners have full knowledge of the balance, however they cannot spend from the balance on their own. As required by the payment template, the wallet needs signatures from both co-owners before funds can be spent.
Let's make a simple transaction. The owner of "Cosigner#0
wallet" will construct and sign a transaction on their end, and then provide a CBOR blob of this transaction to the owner of "Cosigner#1
wallet". Then Cosigner#1
can sign it on their own, and submit it to the network.
We will be using the following shared wallet transaction endpoints:
POST /shared-wallets/{walletId}/transactions-construct
POST /shared-wallets/{walletId}/transactions-sign
POST /shared-wallets/{walletId}/transactions-submit
At any point during the process of constructing and signing a transaction, both "Cosigner#0
" and "Cosigner#1
" may decode the transaction from its CBOR representation using the POST /shared-wallets/{walletId}/transactions-decode
endpoint. Decoding the transaction makes it possible to see all details associated with the transaction, such as its inputs
, outputs
, fees
, deposits
, metadata
, or witness_count
.
Cosigner#0
Constructing
"Cosigner#0
" constructs a transaction sending 10₳ to an external address using the POST /shared-wallets/{walletId}/transactions-construct
endpoint. In response, they receive a CBOR representation of the unsigned transaction.
> curl -X POST http://localhost:8090/v2/shared-wallets/2a0ebd0cceab2161765badf2e389b26e0961de2f/transactions-construct \
-d '{
"payments":[
{
"address":"addr_test1qq86pgrf7yyzp3gysxgqwt4ahegzslygvzh77eq2qwg66pedkztnw78s6gkt3eux35sllasu0x6grejewlrzaus8kekq7cp9ck",
"amount":{
"quantity":10000000,
"unit":"lovelace"
}
}
]
}' \
-H "Content-Type: application/json" | jq .transaction
"hKUAgYJYIP8Fwso5i5ovF8bJJuqZ4xOdvXC3mXJCN2O2vDIxQQOYAAGCogBYOQAPoKBp8QggxQSBkAcuvb5QKHyIYK/vZAoDka0HLbCXN3jw0iy454aNIf/2HHm0geZZd8Yu8ge2bAEaAJiWgKIAWDkwZ/CRYzhreLBgWdOZFReuuejo5RIhvNwLOk51lWQGg+0Ii6yF8VB/6jOUJdwEhwqE3Od9sHl14eQBGwAAAAJTcJsvAhoAArJRAxoBDHtNCAChAYGCAYKCAFgciTss5ZpPDifzTTlpVVbNSaZGERzre1z05XYVWYIAWBzHmpkAwWCRnUTfcTy0aZK2LLLM36Y/4gZMlJhm9fY="
Signing
"Cosigner#0
" signs the transaction with the POST /shared-wallets/{walletId}/transactions-sign
endpoint:
> curl -X POST http://localhost:8090/v2/shared-wallets/2a0ebd0cceab2161765badf2e389b26e0961de2f/transactions-sign \
-d '{
"passphrase":"Secure Passphrase",
"transaction":"hKUAgYJYIP8Fwso5i5ovF8bJJuqZ4xOdvXC3mXJCN2O2vDIxQQOYAAGCogBYOQAPoKBp8QggxQSBkAcuvb5QKHyIYK/vZAoDka0HLbCXN3jw0iy454aNIf/2HHm0geZZd8Yu8ge2bAEaAJiWgKIAWDkwZ/CRYzhreLBgWdOZFReuuejo5RIhvNwLOk51lWQGg+0Ii6yF8VB/6jOUJdwEhwqE3Od9sHl14eQBGwAAAAJTcJsvAhoAArJRAxoBDHzCCAChAYGCAYKCAFgciTss5ZpPDifzTTlpVVbNSaZGERzre1z05XYVWYIAWBzHmpkAwWCRnUTfcTy0aZK2LLLM36Y/4gZMlJhm9fY="
}' \
-H "Content-Type: application/json" | jq .transaction
"hKUAgYJYIP8Fwso5i5ovF8bJJuqZ4xOdvXC3mXJCN2O2vDIxQQOYAAGCogBYOQAPoKBp8QggxQSBkAcuvb5QKHyIYK/vZAoDka0HLbCXN3jw0iy454aNIf/2HHm0geZZd8Yu8ge2bAEaAJiWgKIAWDkwZ/CRYzhreLBgWdOZFReuuejo5RIhvNwLOk51lWQGg+0Ii6yF8VB/6jOUJdwEhwqE3Od9sHl14eQBGwAAAAJTcJsvAhoAArJRAxoBDHzCCACiAIGCWCBOK9nJ9IxJt2gddyZ2fUHC4nre84+EbPQdL60OP0m4ZlhA11gVIBWSlDZl8NQyzV4v9U8AuX8n2UqJK9+Wt1sqnM7jAeYtbuyAN5weTaVV+NDVlpKVg3piowyC1eqZBeWWAQGBggGCggBYHIk7LOWaTw4n8005aVVWzUmmRhEc63tc9OV2FVmCAFgcx5qZAMFgkZ1E33E8tGmStiyyzN+mP+IGTJSYZvX2"
and in response, receives a CBOR representation of the partially-signed transaction.
Cosigner#1
Now "Cosigner#0
" can hand over the CBOR of the partially-signed transaction to "Cosigner#1
", who can then sign it on their own.
Signing
"Cosigner#1
" signs the transaction with POST /shared-wallets/{walletId}/transactions-sign
and gets a CBOR representation of the fully-signed transaction in response.
> curl -X POST http://localhost:8090/v2/shared-wallets/5e46668c320bb4568dd25551e0c33b0539668aa8/transactions-sign \
-d '{
"passphrase":"Secure Passphrase",
"transaction":"hKUAgYJYIP8Fwso5i5ovF8bJJuqZ4xOdvXC3mXJCN2O2vDIxQQOYAAGCogBYOQAPoKBp8QggxQSBkAcuvb5QKHyIYK/vZAoDka0HLbCXN3jw0iy454aNIf/2HHm0geZZd8Yu8ge2bAEaAJiWgKIAWDkwZ/CRYzhreLBgWdOZFReuuejo5RIhvNwLOk51lWQGg+0Ii6yF8VB/6jOUJdwEhwqE3Od9sHl14eQBGwAAAAJTcJsvAhoAArJRAxoBDHzCCACiAIGCWCBOK9nJ9IxJt2gddyZ2fUHC4nre84+EbPQdL60OP0m4ZlhA11gVIBWSlDZl8NQyzV4v9U8AuX8n2UqJK9+Wt1sqnM7jAeYtbuyAN5weTaVV+NDVlpKVg3piowyC1eqZBeWWAQGBggGCggBYHIk7LOWaTw4n8005aVVWzUmmRhEc63tc9OV2FVmCAFgcx5qZAMFgkZ1E33E8tGmStiyyzN+mP+IGTJSYZvX2"
}' \
-H "Content-Type: application/json" | jq .transaction
"hKUAgYJYIP8Fwso5i5ovF8bJJuqZ4xOdvXC3mXJCN2O2vDIxQQOYAAGCogBYOQAPoKBp8QggxQSBkAcuvb5QKHyIYK/vZAoDka0HLbCXN3jw0iy454aNIf/2HHm0geZZd8Yu8ge2bAEaAJiWgKIAWDkwZ/CRYzhreLBgWdOZFReuuejo5RIhvNwLOk51lWQGg+0Ii6yF8VB/6jOUJdwEhwqE3Od9sHl14eQBGwAAAAJTcJsvAhoAArJRAxoBDHzCCACiAIKCWCBOK9nJ9IxJt2gddyZ2fUHC4nre84+EbPQdL60OP0m4ZlhA11gVIBWSlDZl8NQyzV4v9U8AuX8n2UqJK9+Wt1sqnM7jAeYtbuyAN5weTaVV+NDVlpKVg3piowyC1eqZBeWWAYJYIKRn9pUgwcx7GBoIBVJd+9Gh8I/YOwFFK7KyXUXSfrWnWEAqOkD9ljkgYwqwPpuxV+4iJw0hf4PPXA7o9XVxZ4wqT6vSnu1hvsyM4ezuCP/ahTMQ7RzZHTLa1BDx6YawGHQHAYGCAYKCAFgciTss5ZpPDifzTTlpVVbNSaZGERzre1z05XYVWYIAWBzHmpkAwWCRnUTfcTy0aZK2LLLM36Y/4gZMlJhm9fY="
Submission
The transaction is now fully-signed by both co-owners: "Cosigner#0
" and "Cosigner#1
". At this point, either of them can submit it to the network using their respective wallets via the POST /shared-wallets/{walletId}/transactions-submit
endpoint.
> curl -X POST http://localhost:8090/v2/shared-wallets/5e46668c320bb4568dd25551e0c33b0539668aa8/transactions-submit \
-d '{
"transaction":"hKUAgYJYIP8Fwso5i5ovF8bJJuqZ4xOdvXC3mXJCN2O2vDIxQQOYAAGCogBYOQAPoKBp8QggxQSBkAcuvb5QKHyIYK/vZAoDka0HLbCXN3jw0iy454aNIf/2HHm0geZZd8Yu8ge2bAEaAJiWgKIAWDkwZ/CRYzhreLBgWdOZFReuuejo5RIhvNwLOk51lWQGg+0Ii6yF8VB/6jOUJdwEhwqE3Od9sHl14eQBGwAAAAJTcJsvAhoAArJRAxoBDHzCCACiAIKCWCBOK9nJ9IxJt2gddyZ2fUHC4nre84+EbPQdL60OP0m4ZlhA11gVIBWSlDZl8NQyzV4v9U8AuX8n2UqJK9+Wt1sqnM7jAeYtbuyAN5weTaVV+NDVlpKVg3piowyC1eqZBeWWAYJYIKRn9pUgwcx7GBoIBVJd+9Gh8I/YOwFFK7KyXUXSfrWnWEAqOkD9ljkgYwqwPpuxV+4iJw0hf4PPXA7o9XVxZ4wqT6vSnu1hvsyM4ezuCP/ahTMQ7RzZHTLa1BDx6YawGHQHAYGCAYKCAFgciTss5ZpPDifzTTlpVVbNSaZGERzre1z05XYVWYIAWBzHmpkAwWCRnUTfcTy0aZK2LLLM36Y/4gZMlJhm9fY="
}' \
-H "Content-Type: application/json" | jq
{
"id": "d443719bbd3e4301aa34791823b2b7821757e843509d29918006e5ca26ca368c"
}
The transaction has been submitted successfully. The POST /shared-wallets/{walletId}/transactions-submit
endpoint provides a transaction identifier in its response.
We can use this transaction identifier to look up the transaction from either shared wallet using the GET /shared-wallets/{walletId}/transactions
or GET /shared-wallets/{walletId}/transactions/{transactionId}
endpoints.
"Cosigner#0
wallet":
> curl -X GET http://localhost:8090/v2/shared-wallets/2a0ebd0cceab2161765badf2e389b26e0961de2f/transactions/d443719bbd3e4301aa34791823b2b7821757e843509d29918006e5ca26ca368c | jq
{
"amount": {
"quantity": 10176721,
"unit": "lovelace"
},
...
"direction": "outgoing",
...
"fee": {
"quantity": 176721,
"unit": "lovelace"
},
...
"Cosigner#1
wallet":
> curl -X GET http://localhost:8090/v2/shared-wallets/5e46668c320bb4568dd25551e0c33b0539668aa8/transactions/d443719bbd3e4301aa34791823b2b7821757e843509d29918006e5ca26ca368c | jq
{
"amount": {
"quantity": 10176721,
"unit": "lovelace"
},
...
"direction": "outgoing",
...
"fee": {
"quantity": 176721,
"unit": "lovelace"
},
...
Delegation
Delegation of shared wallets will be supported soon.